added proxy/tor preferences

This commit is contained in:
Adithya Abraham Philip 2015-06-05 05:39:12 +05:30
parent 0b3317600b
commit 8f332fad6b
8 changed files with 611 additions and 1 deletions

View File

@ -91,6 +91,17 @@ public final class Constants {
public static final String FILE_USE_COMPRESSION = "useFileCompression";
public static final String TEXT_USE_COMPRESSION = "useTextCompression";
public static final String USE_ARMOR = "useArmor";
// proxy settings
public static final String USE_NORMAL_PROXY = "useNormalProxy";
public static final String USE_TOR_PROXY = "useTorProxy";
public static final String PROXY_HOST = "proxyHost";
public static final String PROXY_PORT = "proxyPort";
}
public static final class ProxyOrbot {
public static final String PROXY_HOST = "127.0.0.1";
public static final int PROXY_HTTP_PORT = 8118;
public static final int PROXY_SOCKS_PORT = 9050;
}
public static final class Defaults {

View File

@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
@ -35,8 +36,11 @@ import android.widget.LinearLayout;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
import java.util.List;
@ -270,6 +274,145 @@ public class SettingsActivity extends PreferenceActivity {
}
}
public static class ProxyPrefsFragment extends PreferenceFragment {
private CheckBoxPreference mUseTor;
private CheckBoxPreference mUseNormalProxy;
private EditTextPreference mProxyHost;
private EditTextPreference mProxyPort;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.proxy_prefs);
mUseTor = (CheckBoxPreference) findPreference(Constants.Pref.USE_TOR_PROXY);
mUseNormalProxy = (CheckBoxPreference) findPreference(Constants.Pref.USE_NORMAL_PROXY);
mProxyHost = (EditTextPreference) findPreference(Constants.Pref.PROXY_HOST);
mProxyPort = (EditTextPreference) findPreference(Constants.Pref.PROXY_PORT);
initializeUseTorPref();
initializeUseNormalProxyPref();
initialiseEditTextPreferences();
if (mUseTor.isChecked()) disableNormalProxyPrefs();
else if (mUseNormalProxy.isChecked()) disableUseTorPrefs();
}
private void initializeUseTorPref() {
mUseTor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if ((Boolean)newValue) {
OrbotHelper orbotHelper = new OrbotHelper(ProxyPrefsFragment.this.getActivity());
boolean installed = orbotHelper.isOrbotInstalled();
if (!installed) {
Log.d(Constants.TAG, "Prompting to install Tor");
orbotHelper.promptToInstall(ProxyPrefsFragment.this.getActivity());
// don't let the user check the box until he's installed orbot
return false;
} else {
disableNormalProxyPrefs();
// let the enable tor box be checked
return true;
}
}
else {
// we're unchecking Tor, so enable other proxy
enableNormalProxyPrefs();
return true;
}
}
});
}
private void initializeUseNormalProxyPref() {
mUseNormalProxy.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if ((Boolean) newValue) {
disableUseTorPrefs();
} else {
enableUseTorPrefs();
}
return true;
}
});
}
private void initialiseEditTextPreferences() {
mProxyHost.setSummary(mProxyHost.getText());
mProxyPort.setSummary(mProxyPort.getText());
mProxyHost.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue.equals("")) {
Notify.create(
ProxyPrefsFragment.this.getActivity(),
R.string.pref_proxy_host_err_invalid,
Notify.Style.ERROR
).show();
return false;
} else {
mProxyHost.setSummary((CharSequence) newValue);
return true;
}
}
});
mProxyPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
try {
int port = Integer.parseInt((String) newValue);
if(port < 0 || port > 65535) {
Notify.create(
ProxyPrefsFragment.this.getActivity(),
R.string.pref_proxy_port_err_invalid,
Notify.Style.ERROR
).show();
return false;
}
// no issues, save port
mProxyPort.setSummary("" + port);
return true;
} catch (NumberFormatException e) {
Notify.create(
ProxyPrefsFragment.this.getActivity(),
R.string.pref_proxy_port_err_invalid,
Notify.Style.ERROR
).show();
return false;
}
}
});
}
private void disableNormalProxyPrefs() {
mUseNormalProxy.setChecked(false);
mUseNormalProxy.setEnabled(false);
mProxyHost.setEnabled(false);
mProxyPort.setEnabled(false);
}
private void enableNormalProxyPrefs() {
mUseNormalProxy.setEnabled(true);
mProxyHost.setEnabled(true);
mProxyPort.setEnabled(true);
}
private void disableUseTorPrefs() {
mUseTor.setChecked(false);
mUseTor.setEnabled(false);
}
private void enableUseTorPrefs() {
mUseTor.setEnabled(true);
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
protected boolean isValidFragment(String fragmentName) {
return AdvancedPrefsFragment.class.getName().equals(fragmentName)
@ -332,7 +475,8 @@ public class SettingsActivity extends PreferenceActivity {
String[] servers = sPreferences.getKeyServers();
String serverSummary = context.getResources().getQuantityString(
R.plurals.n_keyservers, servers.length, servers.length);
return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver();
return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences
.getPreferredKeyserver();
}
private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) {

View File

@ -224,6 +224,53 @@ public class Preferences {
return mSharedPreferences.getBoolean(Pref.ENCRYPT_FILENAMES, true);
}
public boolean getUseNormalProxy() {
return mSharedPreferences.getBoolean(Constants.Pref.USE_NORMAL_PROXY, false);
}
public void setUseNormalProxy(boolean use) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Constants.Pref.USE_NORMAL_PROXY, use);
editor.commit();
}
public boolean getUseTorProxy() {
return mSharedPreferences.getBoolean(Constants.Pref.USE_TOR_PROXY, false);
}
public void setUseTorProxy(boolean use) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Constants.Pref.USE_TOR_PROXY, use);
editor.commit();
}
public String getProxyHost() {
return mSharedPreferences.getString(Constants.Pref.PROXY_HOST, null);
}
public void setProxyHost(String host) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(Constants.Pref.PROXY_HOST, host);
editor.commit();
}
/**
* we store port as String for easy interfacing with EditTextPreference, but return it as an integer
* @return port number of proxy
*/
public int getProxyPort() {
return Integer.parseInt(mSharedPreferences.getString(Pref.PROXY_PORT, "-1"));
}
/**
* we store port as String for easy interfacing with EditTextPreference, but return it as an integer
* @param port proxy port
*/
public void setProxyPort(String port) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(Pref.PROXY_PORT, port);
editor.commit();
}
public CloudSearchPrefs getCloudSearchPrefs() {
return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true),
mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true),

View File

@ -0,0 +1,124 @@
package org.sufficientlysecure.keychain.util.orbot;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import org.sufficientlysecure.keychain.R;
public class OrbotHelper {
private final static int REQUEST_CODE_STATUS = 100;
public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
public final static String TOR_BIN_PATH = "/data/data/org.torproject.android/app_bin/tor";
public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
public final static String ACTION_REQUEST_HS = "org.torproject.android.REQUEST_HS_PORT";
public final static int HS_REQUEST_CODE = 9999;
private Context mContext = null;
public OrbotHelper(Context context)
{
mContext = context;
}
public boolean isOrbotRunning()
{
int procId = TorServiceUtils.findProcessId(TOR_BIN_PATH);
return (procId != -1);
}
public boolean isOrbotInstalled()
{
return isAppInstalled(ORBOT_PACKAGE_NAME);
}
private boolean isAppInstalled(String uri) {
PackageManager pm = mContext.getPackageManager();
boolean installed = false;
try {
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
installed = true;
} catch (PackageManager.NameNotFoundException e) {
installed = false;
}
return installed;
}
public void promptToInstall(Activity activity)
{
String uriMarket = activity.getString(R.string.market_orbot);
// show dialog - install from market, f-droid or direct APK
showDownloadDialog(activity, activity.getString(R.string.install_orbot_),
activity.getString(R.string.you_must_have_orbot),
activity.getString(R.string.orbot_yes), activity.getString(R.string.orbot_no), uriMarket);
}
private static AlertDialog showDownloadDialog(final Activity activity,
CharSequence stringTitle, CharSequence stringMessage, CharSequence stringButtonYes,
CharSequence stringButtonNo, final String uriString) {
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
downloadDialog.setTitle(stringTitle);
downloadDialog.setMessage(stringMessage);
downloadDialog.setPositiveButton(stringButtonYes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = Uri.parse(uriString);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
activity.startActivity(intent);
}
});
downloadDialog.setNegativeButton(stringButtonNo, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
return downloadDialog.show();
}
public void requestOrbotStart(final Activity activity)
{
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
downloadDialog.setTitle(R.string.start_orbot_);
downloadDialog
.setMessage(R.string.orbot_doesn_t_appear_to_be_running_would_you_like_to_start_it_up_and_connect_to_tor_);
downloadDialog.setPositiveButton(R.string.orbot_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
activity.startActivityForResult(getOrbotStartIntent(), 1);
}
});
downloadDialog.setNegativeButton(R.string.orbot_no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
downloadDialog.show();
}
public void requestHiddenServiceOnPort(Activity activity, int port)
{
Intent intent = new Intent(ACTION_REQUEST_HS);
intent.setPackage(ORBOT_PACKAGE_NAME);
intent.putExtra("hs_port", port);
activity.startActivityForResult(intent, HS_REQUEST_CODE);
}
public static Intent getOrbotStartIntent() {
Intent intent = new Intent(ACTION_START_TOR);
intent.setPackage(ORBOT_PACKAGE_NAME);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
}

View File

@ -0,0 +1,234 @@
/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
/* See LICENSE for licensing information */
package org.sufficientlysecure.keychain.util.orbot;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URLEncoder;
import java.util.StringTokenizer;
import android.util.Log;
public class TorServiceUtils {
private final static String TAG = "TorUtils";
// various console cmds
public final static String SHELL_CMD_CHMOD = "chmod";
public final static String SHELL_CMD_KILL = "kill -9";
public final static String SHELL_CMD_RM = "rm";
public final static String SHELL_CMD_PS = "ps";
public final static String SHELL_CMD_PIDOF = "pidof";
public final static String CHMOD_EXE_VALUE = "700";
public static boolean isRootPossible()
{
StringBuilder log = new StringBuilder();
try {
// Check if Superuser.apk exists
File fileSU = new File("/system/app/Superuser.apk");
if (fileSU.exists())
return true;
fileSU = new File("/system/app/superuser.apk");
if (fileSU.exists())
return true;
fileSU = new File("/system/bin/su");
if (fileSU.exists())
{
String[] cmd = {
"su"
};
int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true);
if (exitCode != 0)
return false;
else
return true;
}
// Check for 'su' binary
String[] cmd = {
"which su"
};
int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true);
if (exitCode == 0) {
Log.d(TAG, "root exists, but not sure about permissions");
return true;
}
} catch (IOException e) {
// this means that there is no root to be had (normally) so we won't
// log anything
Log.e(TAG, "Error checking for root access", e);
} catch (Exception e) {
Log.e(TAG, "Error checking for root access", e);
// this means that there is no root to be had (normally)
}
Log.e(TAG, "Could not acquire root permissions");
return false;
}
public static int findProcessId(String command)
{
int procId = -1;
try
{
procId = findProcessIdWithPidOf(command);
if (procId == -1)
procId = findProcessIdWithPS(command);
} catch (Exception e)
{
try
{
procId = findProcessIdWithPS(command);
} catch (Exception e2)
{
Log.e(TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2);
}
}
return procId;
}
// use 'pidof' command
public static int findProcessIdWithPidOf(String command) throws Exception
{
int procId = -1;
Runtime r = Runtime.getRuntime();
Process procPs = null;
String baseName = new File(command).getName();
// fix contributed my mikos on 2010.12.10
procPs = r.exec(new String[] {
SHELL_CMD_PIDOF, baseName
});
// procPs = r.exec(SHELL_CMD_PIDOF);
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null)
{
try
{
// this line should just be the process id
procId = Integer.parseInt(line.trim());
break;
} catch (NumberFormatException e)
{
Log.e("TorServiceUtils", "unable to parse process pid: " + line, e);
}
}
return procId;
}
// use 'ps' command
public static int findProcessIdWithPS(String command) throws Exception
{
int procId = -1;
Runtime r = Runtime.getRuntime();
Process procPs = null;
procPs = r.exec(SHELL_CMD_PS);
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null)
{
if (line.indexOf(' ' + command) != -1)
{
StringTokenizer st = new StringTokenizer(line, " ");
st.nextToken(); // proc owner
procId = Integer.parseInt(st.nextToken().trim());
break;
}
}
return procId;
}
public static int doShellCommand(String[] cmds, StringBuilder log, boolean runAsRoot,
boolean waitFor) throws Exception
{
Process proc = null;
int exitCode = -1;
if (runAsRoot)
proc = Runtime.getRuntime().exec("su");
else
proc = Runtime.getRuntime().exec("sh");
OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream());
for (int i = 0; i < cmds.length; i++)
{
// TorService.logMessage("executing shell cmd: " + cmds[i] +
// "; runAsRoot=" + runAsRoot + ";waitFor=" + waitFor);
out.write(cmds[i]);
out.write("\n");
}
out.flush();
out.write("exit\n");
out.flush();
if (waitFor)
{
final char buf[] = new char[10];
// Consume the "stdout"
InputStreamReader reader = new InputStreamReader(proc.getInputStream());
int read = 0;
while ((read = reader.read(buf)) != -1) {
if (log != null)
log.append(buf, 0, read);
}
// Consume the "stderr"
reader = new InputStreamReader(proc.getErrorStream());
read = 0;
while ((read = reader.read(buf)) != -1) {
if (log != null)
log.append(buf, 0, read);
}
exitCode = proc.waitFor();
}
return exitCode;
}
}

View File

@ -49,6 +49,7 @@
<string name="section_keys">"Subkeys"</string>
<string name="section_cloud_search">"Cloud search"</string>
<string name="section_passphrase_cache">"Password Cache"</string>
<string name="section_proxy_settings">"Proxy Settings"</string>
<string name="section_certify">"Confirm"</string>
<string name="section_actions">"Actions"</string>
<string name="section_share_key">"Key"</string>
@ -165,6 +166,16 @@
<string name="pref_keybase">"keybase.io"</string>
<string name="pref_keybase_summary">"Search keys on keybase.io"</string>
<!-- Proxy Preferences -->
<string name="pref_proxy_non">"Don't use a proxy"</string>
<string name="pref_proxy_tor">"Enable Tor"</string>
<string name="pref_proxy_tor_summary">"Requires Orbot to be installed"</string>
<string name="pref_proxy_normal">"Enable other proxy"</string>
<string name="pref_proxy_host">"Proxy Host"</string>
<string name="pref_proxy_host_err_invalid">"Proxy host cannot be empty"</string>
<string name="pref_proxy_port">"Proxy Port"</string>
<string name="pref_proxy_port_err_invalid">"Invalid port number entered"</string>
<string name="user_id_no_name">"&lt;no name&gt;"</string>
<string name="none">"&lt;none&gt;"</string>
@ -180,6 +191,15 @@
<string name="secret_key">"Secret Key:"</string>
<!-- OrbotHelper strings -->
<string name="install_orbot_">"Install Orbot?"</string>
<string name="market_orbot">"market://search?q=pname:org.torproject.android"</string>
<string name="you_must_have_orbot">"You must have Orbot installed and activated to proxy traffic through it. Would you like to install it from Google Play?"</string>
<string name="orbot_yes">"Yes"</string>
<string name="orbot_no">"No"</string>
<string name="start_orbot_">"Start Orbot?"</string>
<string name="orbot_doesn_t_appear_to_be_running_would_you_like_to_start_it_up_and_connect_to_tor_">"Orbot doesn\'t appear to be running. Would you like to start it up and connect to Tor?"</string>
<!-- choice -->
<string name="choice_none">"None"</string>
<string name="choice_15secs">"15 secs"</string>

View File

@ -5,4 +5,7 @@
<header
android:fragment="org.sufficientlysecure.keychain.ui.SettingsActivity$AdvancedPrefsFragment"
android:title="@string/section_passphrase_cache" />
<header
android:fragment="org.sufficientlysecure.keychain.ui.SettingsActivity$ProxyPrefsFragment"
android:title="@string/section_proxy_settings" />
</preference-headers>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="useTorProxy"
android:persistent="true"
android:title="@string/pref_proxy_tor"
android:summary="@string/pref_proxy_tor_summary" />
<CheckBoxPreference
android:key="useNormalProxy"
android:persistent="true"
android:title="@string/pref_proxy_normal" />
<EditTextPreference
android:key="proxyHost"
android:persistent="true"
android:defaultValue="127.0.0.1"
android:title="@string/pref_proxy_host"
android:cursorVisible="true"
android:textCursorDrawable="@null"
android:inputType="textEmailAddress"/>
<EditTextPreference
android:key="proxyPort"
android:defaultValue="8118"
android:persistent="true"
android:title="@string/pref_proxy_port"
android:textCursorDrawable="@null"
android:inputType="number" />
</PreferenceScreen>