aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDominik Schürmann <dominik@dominikschuermann.de>2015-08-06 10:34:13 +0200
committerDominik Schürmann <dominik@dominikschuermann.de>2015-08-06 10:34:13 +0200
commit3dbe0033dcf090b0c34f67c2f7440e3023eba060 (patch)
tree8cb7709cdfccc9275d376febc649c0118a808f6b
parent8fb3d1e16612cc63cb58a4cbbab38b08e0469ee0 (diff)
parent37864a9d42436081eb6132dc0c8b1e0868a15272 (diff)
downloadopen-keychain-3dbe0033dcf090b0c34f67c2f7440e3023eba060.tar.gz
open-keychain-3dbe0033dcf090b0c34f67c2f7440e3023eba060.tar.bz2
open-keychain-3dbe0033dcf090b0c34f67c2f7440e3023eba060.zip
Merge pull request #1473 from open-keychain/orbot-helper
Updated OrbotHelper with the latest NetCipher
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java46
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java81
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java19
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java73
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java339
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java26
-rw-r--r--OpenKeychain/src/main/res/values/strings.xml2
11 files changed, 468 insertions, 169 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
index 53e5efabe..8502798cd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
@@ -371,32 +371,38 @@ public class ImportKeysListFragment extends ListFragment implements
Runnable showOrbotDialog = new Runnable() {
@Override
public void run() {
- final Runnable ignoreTor = new Runnable() {
- @Override
- public void run() {
- mParcelableProxy = ParcelableProxy
- .getForNoProxy();
- mShowingOrbotDialog = false;
- restartLoaders();
- }
- };
-
- final Runnable dialogDismiss = new Runnable() {
- @Override
- public void run() {
- mShowingOrbotDialog = false;
- }
- };
-
- if (OrbotHelper.putOrbotInRequiredState(
- ignoreTor, dialogDismiss, getActivity())) {
+ OrbotHelper.DialogActions dialogActions =
+ new OrbotHelper.DialogActions() {
+ @Override
+ public void onOrbotStarted() {
+ mShowingOrbotDialog = false;
+ restartLoaders();
+ }
+
+ @Override
+ public void onNeutralButton() {
+ mParcelableProxy = ParcelableProxy
+ .getForNoProxy();
+ mShowingOrbotDialog = false;
+ restartLoaders();
+ }
+
+ @Override
+ public void onCancel() {
+ mShowingOrbotDialog = false;
+ }
+ };
+
+ if (OrbotHelper.putOrbotInRequiredState(dialogActions,
+ getActivity())) {
// looks like we didn't have to show the
// dialog after all
+ mShowingOrbotDialog = false;
restartLoaders();
}
}
};
- new Handler().post(showOrbotDialog );
+ new Handler().post(showOrbotDialog);
mShowingOrbotDialog = true;
}
} else {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java
index 587044659..d1c247a76 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java
@@ -22,18 +22,20 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
-import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
-import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
/**
* Simply encapsulates a dialog. If orbot is not installed, it shows an install dialog, else a
* dialog to enable orbot.
*/
-public class OrbotRequiredDialogActivity extends FragmentActivity {
+public class OrbotRequiredDialogActivity extends FragmentActivity
+ implements OrbotHelper.DialogActions {
+
+ // if suppplied and true will start Orbot directly without showing dialog
+ public static final String EXTRA_START_ORBOT = "start_orbot";
// to provide any previous crypto input into which proxy preference is merged
public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input";
@@ -45,47 +47,68 @@ public class OrbotRequiredDialogActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
if (mCryptoInputParcel == null) {
+ // compatibility with usages that don't use a CryptoInputParcel
mCryptoInputParcel = new CryptoInputParcel();
}
- showDialog();
+
+ boolean startOrbotDirect = getIntent().getBooleanExtra(EXTRA_START_ORBOT, false);
+ if (startOrbotDirect) {
+ OrbotHelper.bestPossibleOrbotStart(this, this);
+ } else {
+ showDialog();
+ }
}
/**
- * Displays an install or start orbot dialog depending on orbot's presence and state
+ * Displays an install or start orbot dialog (or silent orbot start) depending on orbot's
+ * presence and state
*/
public void showDialog() {
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() {
- Runnable ignoreTor = new Runnable() {
- @Override
- public void run() {
- Intent intent = new Intent();
- mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy());
- intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
- setResult(RESULT_OK, intent);
- finish();
- }
- };
-
- Runnable dialogDismissed = new Runnable() {
- @Override
- public void run() {
- finish();
- }
- };
-
- if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, dialogDismissed,
- Preferences.getPreferences(OrbotRequiredDialogActivity.this)
- .getProxyPrefs(),
+
+ if (OrbotHelper.putOrbotInRequiredState(OrbotRequiredDialogActivity.this,
OrbotRequiredDialogActivity.this)) {
// no action required after all
- Intent intent = new Intent();
- intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
- setResult(RESULT_OK, intent);
+ onOrbotStarted();
}
}
});
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case OrbotHelper.START_TOR_RESULT: {
+ onOrbotStarted(); // assumption that orbot was started, no way to tell for sure
+ }
+ }
+ }
+
+ @Override
+ public void onOrbotStarted() {
+ Intent intent = new Intent();
+ // send back unmodified CryptoInputParcel for a retry
+ intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public void onNeutralButton() {
+ Intent intent = new Intent();
+ mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy());
+ intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public void onCancel() {
+ finish();
+ }
} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
index 2ae64d90b..d8edbe4f8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
@@ -250,9 +250,6 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC
public class KeyserverListAdapter extends RecyclerView.Adapter<KeyserverListAdapter.ViewHolder>
implements ItemTouchHelperAdapter {
- // to update the ViewHolder associated with first item, for when an item is deleted
- private ViewHolder mFirstItem;
-
private final List<String> mKeyservers;
public KeyserverListAdapter(List<String> keyservers) {
@@ -263,15 +260,11 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.settings_keyserver_item, parent, false);
- ViewHolder viewHolder = new ViewHolder(view);
- return viewHolder;
+ return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
- if (position == 0) {
- mFirstItem = holder;
- }
holder.keyserverUrl.setText(mKeyservers.get(position));
// Start a drag whenever the handle view it touched
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java
index b118c3ad0..150acdc90 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java
@@ -200,19 +200,30 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
mStartSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity())
- .getProxyPrefs();
+ final Preferences.ProxyPrefs proxyPrefs =
+ Preferences.getPreferences(getActivity()).getProxyPrefs();
- Runnable ignoreTor = new Runnable() {
+ OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
@Override
- public void run() {
+ public void onOrbotStarted() {
mStartSearch.setEnabled(false);
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
}
+
+ @Override
+ public void onNeutralButton() {
+ mStartSearch.setEnabled(false);
+ new DescribeKey(ParcelableProxy.getForNoProxy())
+ .execute(fingerprint);
+ }
+
+ @Override
+ public void onCancel() {
+
+ }
};
- if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs,
- getActivity())) {
+ if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) {
mStartSearch.setEnabled(false);
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
index 691cc009d..47bc7dfda 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
@@ -50,6 +50,7 @@ import android.widget.TextView.OnEditorActionListener;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
+
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
@@ -216,15 +217,25 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
if (mVerifyKeyserverCheckBox.isChecked()) {
final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity())
.getProxyPrefs();
- Runnable ignoreTor = new Runnable() {
+ OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
+ @Override
+ public void onOrbotStarted() {
+ verifyConnection(keyserverUrl,
+ proxyPrefs.parcelableProxy.getProxy());
+ }
+
@Override
- public void run() {
+ public void onNeutralButton() {
verifyConnection(keyserverUrl, null);
}
+
+ @Override
+ public void onCancel() {
+ // do nothing
+ }
};
- if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs,
- getActivity())) {
+ if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) {
verifyConnection(keyserverUrl, proxyPrefs.parcelableProxy.getProxy());
}
} else {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java
index d1d22b6d7..b06e05c30 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java
@@ -18,15 +18,19 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
-import android.support.v7.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
+import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.widget.Button;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -43,8 +47,11 @@ public class OrbotStartDialogFragment extends DialogFragment {
private static final String ARG_MESSAGE = "message";
private static final String ARG_MIDDLE_BUTTON = "middleButton";
+ private static final int ORBOT_REQUEST_CODE = 1;
+
public static final int MESSAGE_MIDDLE_BUTTON = 1;
- public static final int MESSAGE_DIALOG_DISMISSED = 2; // for either cancel or enable pressed
+ public static final int MESSAGE_DIALOG_CANCELLED = 2; // for either cancel or enable pressed
+ public static final int MESSAGE_ORBOT_STARTED = 3; // for either cancel or enable pressed
public static OrbotStartDialogFragment newInstance(Messenger messenger, int title, int message, int middleButton) {
Bundle args = new Bundle();
@@ -59,6 +66,7 @@ public class OrbotStartDialogFragment extends DialogFragment {
return fragment;
}
+ @NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
@@ -77,7 +85,7 @@ public class OrbotStartDialogFragment extends DialogFragment {
@Override
public void onClick(DialogInterface dialog, int which) {
Message msg = Message.obtain();
- msg.what = MESSAGE_DIALOG_DISMISSED;
+ msg.what = MESSAGE_DIALOG_CANCELLED;
try {
messenger.send(msg);
} catch (RemoteException e) {
@@ -89,13 +97,11 @@ public class OrbotStartDialogFragment extends DialogFragment {
}
});
- builder.setPositiveButton(R.string.orbot_start_dialog_start, new DialogInterface.OnClickListener() {
+ builder.setNeutralButton(middleButton, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- getActivity().startActivityForResult(OrbotHelper.getOrbotStartIntent(), 1);
-
- Message msg = Message.obtain();
- msg.what = MESSAGE_DIALOG_DISMISSED;
+ Message msg = new Message();
+ msg.what = MESSAGE_MIDDLE_BUTTON;
try {
messenger.send(msg);
} catch (RemoteException e) {
@@ -106,21 +112,52 @@ public class OrbotStartDialogFragment extends DialogFragment {
}
});
- builder.setNeutralButton(middleButton, new DialogInterface.OnClickListener() {
+ builder.setPositiveButton(R.string.orbot_start_dialog_start, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- Message msg = new Message();
- msg.what = MESSAGE_MIDDLE_BUTTON;
- try {
- messenger.send(msg);
- } catch (RemoteException e) {
- Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
- } catch (NullPointerException e) {
- Log.w(Constants.TAG, "Messenger is null!", e);
- }
+ // actual onClick defined in onStart, this is just to make the button appear
}
});
return builder.show();
}
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ //super.onStart() is where dialog.show() is actually called on the underlying dialog,
+ // so we have to do it after this point
+ AlertDialog d = (AlertDialog) getDialog();
+ if (d != null) {
+ Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
+ positiveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ startActivityForResult(OrbotHelper.getShowOrbotStartIntent(),
+ ORBOT_REQUEST_CODE);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == ORBOT_REQUEST_CODE) {
+ // assume Orbot was started
+ final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER);
+
+ Message msg = Message.obtain();
+ msg.what = MESSAGE_ORBOT_STARTED;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ dismiss();
+ }
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java
index b2b71b364..82d1be4ed 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Dialog;
import android.os.Bundle;
import android.os.Messenger;
+import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper;
@@ -34,7 +35,7 @@ public class SupportInstallDialogFragment extends DialogFragment {
* and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set.
*
* @param messenger required only for callback from middle button if it has been set
- * @param title
+ * @param title xml resource for title of the install dialog
* @param message content of dialog
* @param packageToInstall package name of application to install
* @param middleButton if not null, adds a third button to the app with a call back
@@ -57,16 +58,17 @@ public class SupportInstallDialogFragment extends DialogFragment {
/**
* To create a DialogFragment with only two buttons
*
- * @param title
- * @param message
- * @param packageToInstall
+ * @param title xml string resource for title of the dialog
+ * @param message xml string resource to display as dialog body
+ * @param packageToInstall name of package to install
* @return
*/
public static SupportInstallDialogFragment newInstance(int title, int message,
- String packageToInstall) {
+ String packageToInstall) {
return newInstance(null, title, message, packageToInstall, -1, false);
}
+ @NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
index b00b03ec7..7e788d04c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
@@ -51,8 +51,11 @@ public class ParcelableProxy implements Parcelable {
if (mProxyHost == null) {
return null;
}
-
- return new Proxy(mProxyType, new InetSocketAddress(mProxyHost, mProxyPort));
+ /*
+ * InetSocketAddress.createUnresolved so we can use this method even in the main thread
+ * (no network call)
+ */
+ return new Proxy(mProxyType, InetSocketAddress.createUnresolved(mProxyHost, mProxyPort));
}
protected ParcelableProxy(Parcel in) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java
index 9eb92f92f..d5ee75f9b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java
@@ -49,66 +49,216 @@
package org.sufficientlysecure.keychain.util.orbot;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
+import android.text.TextUtils;
+import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.dialog.SupportInstallDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.OrbotStartDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.PreferenceInstallDialogFragment;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
+import java.util.List;
+
/**
* This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/
*/
public class OrbotHelper {
+ public interface DialogActions {
+ void onOrbotStarted();
+
+ void onNeutralButton();
+
+ void onCancel();
+ }
+
+ 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 ORBOT_MARKET_URI = "market://details?id=" + ORBOT_PACKAGE_NAME;
+ public final static String ORBOT_FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
+ + ORBOT_PACKAGE_NAME;
+ public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
+ + ORBOT_PACKAGE_NAME;
+
+ /**
+ * A request to Orbot to transparently start Tor services
+ */
+ public final static String ACTION_START = "org.torproject.android.intent.action.START";
+ /**
+ * {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
+ */
+ public final static String ACTION_STATUS = "org.torproject.android.intent.action.STATUS";
+ /**
+ * {@code String} that contains a status constant: {@link #STATUS_ON},
+ * {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
+ * {@link #STATUS_STOPPING}
+ */
+ public final static String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS";
+ /**
+ * A {@link String} {@code packageName} for Orbot to direct its status reply
+ * to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
+ */
+ public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME";
+
+ /**
+ * All tor-related services and daemons are stopped
+ */
+ @SuppressWarnings("unused") // we might use this later, sent by Orbot
+ public final static String STATUS_OFF = "OFF";
+ /**
+ * All tor-related services and daemons have completed starting
+ */
+ public final static String STATUS_ON = "ON";
+ @SuppressWarnings("unused") // we might use this later, sent by Orbot
+ public final static String STATUS_STARTING = "STARTING";
+ @SuppressWarnings("unused") // we might use this later, sent by Orbot
+ public final static String STATUS_STOPPING = "STOPPING";
+ /**
+ * The user has disabled the ability for background starts triggered by
+ * apps. Fallback to the old Intent that brings up Orbot.
+ */
+ public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
+ /**
+ * request code used to start tor
+ */
+ public final static int START_TOR_RESULT = 0x9234;
- public static boolean isOrbotRunning() {
- int procId = TorServiceUtils.findProcessId(TOR_BIN_PATH);
+ private final static String FDROID_PACKAGE_NAME = "org.fdroid.fdroid";
+ private final static String PLAY_PACKAGE_NAME = "com.android.vending";
+
+ private OrbotHelper() {
+ // only static utility methods, do not instantiate
+ }
+
+ public static boolean isOrbotRunning(Context context) {
+ int procId = TorServiceUtils.findProcessId(context);
return (procId != -1);
}
public static boolean isOrbotInstalled(Context context) {
- return isAppInstalled(ORBOT_PACKAGE_NAME, context);
+ return isAppInstalled(context, ORBOT_PACKAGE_NAME);
}
- private static boolean isAppInstalled(String uri, Context context) {
- PackageManager pm = context.getPackageManager();
-
- boolean installed;
+ private static boolean isAppInstalled(Context context, String uri) {
try {
+ PackageManager pm = context.getPackageManager();
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
- installed = true;
+ return true;
} catch (PackageManager.NameNotFoundException e) {
- installed = false;
+ return false;
}
- return installed;
}
/**
- * hack to get around the fact that PreferenceActivity still supports only android.app.DialogFragment
+ * First, checks whether Orbot is installed, then checks whether Orbot is
+ * running. If Orbot is installed and not running, then an {@link Intent} is
+ * sent to request Orbot to start, which will show the main Orbot screen.
+ * The result will be returned in
+ * {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
+ * with a {@code requestCode} of {@code START_TOR_RESULT}
*
- * @return
+ * @param activity the {@link Activity} that gets the
+ * {@code START_TOR_RESULT} result
+ * @return whether the start request was sent to Orbot
*/
- public static android.app.DialogFragment getPreferenceInstallDialogFragment() {
- return PreferenceInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title,
- R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME);
+ public static boolean requestShowOrbotStart(Activity activity) {
+ if (OrbotHelper.isOrbotInstalled(activity)) {
+ if (!OrbotHelper.isOrbotRunning(activity)) {
+ Intent intent = getShowOrbotStartIntent();
+ activity.startActivityForResult(intent, START_TOR_RESULT);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static Intent getShowOrbotStartIntent() {
+ Intent intent = new Intent(ACTION_START_TOR);
+ intent.setPackage(ORBOT_PACKAGE_NAME);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ /**
+ * First, checks whether Orbot is installed. If Orbot is installed, then a
+ * broadcast {@link Intent} is sent to request Orbot to start transparently
+ * in the background. When Orbot receives this {@code Intent}, it will
+ * immediately reply to this all with its status via an
+ * {@link #ACTION_STATUS} {@code Intent} that is broadcast to the
+ * {@code packageName} of the provided {@link Context} (i.e.
+ * {@link Context#getPackageName()}.
+ *
+ * @param context the app {@link Context} will receive the reply
+ * @return whether the start request was sent to Orbot
+ */
+ public static boolean requestStartTor(Context context) {
+ if (OrbotHelper.isOrbotInstalled(context)) {
+ Log.i("OrbotHelper", "requestStartTor " + context.getPackageName());
+ Intent intent = getOrbotStartIntent();
+ intent.putExtra(EXTRA_PACKAGE_NAME, context.getPackageName());
+ context.sendBroadcast(intent);
+ return true;
+ }
+ return false;
+ }
+
+ public static Intent getOrbotStartIntent() {
+ Intent intent = new Intent(ACTION_START);
+ intent.setPackage(ORBOT_PACKAGE_NAME);
+ return intent;
+ }
+
+ public static Intent getOrbotInstallIntent(Context context) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(ORBOT_MARKET_URI));
+
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
+
+ String foundPackageName = null;
+ for (ResolveInfo r : resInfos) {
+ Log.i("OrbotHelper", "market: " + r.activityInfo.packageName);
+ if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
+ || TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
+ foundPackageName = r.activityInfo.packageName;
+ break;
+ }
+ }
+
+ if (foundPackageName == null) {
+ intent.setData(Uri.parse(ORBOT_FDROID_URI));
+ } else {
+ intent.setPackage(foundPackageName);
+ }
+ return intent;
}
- public static DialogFragment getInstallDialogFragment() {
- return SupportInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title,
+ /**
+ * hack to get around the fact that PreferenceActivity still supports only android.app.DialogFragment
+ */
+ public static android.app.DialogFragment getPreferenceInstallDialogFragment() {
+ return PreferenceInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title,
R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME);
}
@@ -123,13 +273,6 @@ public class OrbotHelper {
middleButton);
}
- 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;
- }
-
/**
* checks preferences to see if Orbot is required, and if yes, if it is installed and running
*
@@ -141,26 +284,23 @@ public class OrbotHelper {
Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(context).getProxyPrefs();
if (!proxyPrefs.torEnabled) {
return true;
- } else if (!OrbotHelper.isOrbotInstalled(context) || !OrbotHelper.isOrbotRunning()) {
+ } else if (!OrbotHelper.isOrbotInstalled(context) || !OrbotHelper.isOrbotRunning(context)) {
return false;
}
return true;
}
/**
- * checks if Tor is enabled and if it is, that Orbot is installed and runnign. Generates appropriate dialogs.
+ * checks if Tor is enabled and if it is, that Orbot is installed and running. Generates appropriate dialogs.
*
- * @param middleButton resourceId of string to display as the middle button of install and enable dialogs
- * @param middleButtonRunnable runnable to be executed if the user clicks on the middle button
- * @param proxyPrefs
- * @param fragmentActivity
+ * @param middleButton resourceId of string to display as the middle button of install and enable dialogs
+ * @param proxyPrefs proxy preferences used to determine if Tor is required to be started
* @return true if Tor is not enabled or Tor is enabled and Orbot is installed and running, else false
*/
public static boolean putOrbotInRequiredState(final int middleButton,
- final Runnable middleButtonRunnable,
- final Runnable dialogDismissRunnable,
+ final DialogActions dialogActions,
Preferences.ProxyPrefs proxyPrefs,
- FragmentActivity fragmentActivity) {
+ final FragmentActivity fragmentActivity) {
if (!proxyPrefs.torEnabled) {
return true;
@@ -172,10 +312,12 @@ public class OrbotHelper {
public void handleMessage(Message msg) {
switch (msg.what) {
case SupportInstallDialogFragment.MESSAGE_MIDDLE_CLICKED:
- middleButtonRunnable.run();
+ dialogActions.onNeutralButton();
break;
case SupportInstallDialogFragment.MESSAGE_DIALOG_DISMISSED:
- dialogDismissRunnable.run();
+ // both install and cancel buttons mean we don't go ahead with an
+ // operation, so it's okay to cancel
+ dialogActions.onCancel();
break;
}
}
@@ -187,25 +329,39 @@ public class OrbotHelper {
).show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotInstallDialog");
return false;
- } else if (!OrbotHelper.isOrbotRunning()) {
+ } else if (!OrbotHelper.isOrbotRunning(fragmentActivity)) {
- Handler ignoreTorHandler = new Handler() {
+ final Handler dialogHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case OrbotStartDialogFragment.MESSAGE_MIDDLE_BUTTON:
- middleButtonRunnable.run();
+ dialogActions.onNeutralButton();
+ break;
+ case OrbotStartDialogFragment.MESSAGE_DIALOG_CANCELLED:
+ dialogActions.onCancel();
break;
- case OrbotStartDialogFragment.MESSAGE_DIALOG_DISMISSED:
- dialogDismissRunnable.run();
+ case OrbotStartDialogFragment.MESSAGE_ORBOT_STARTED:
+ dialogActions.onOrbotStarted();
break;
}
}
};
- OrbotHelper.getOrbotStartDialogFragment(new Messenger(ignoreTorHandler),
- middleButton)
- .show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotStartDialog");
+ new SilentStartManager() {
+
+ @Override
+ protected void onOrbotStarted() {
+ dialogActions.onOrbotStarted();
+ }
+
+ @Override
+ protected void onSilentStartDisabled() {
+ getOrbotStartDialogFragment(new Messenger(dialogHandler), middleButton)
+ .show(fragmentActivity.getSupportFragmentManager(),
+ "OrbotHelperOrbotStartDialog");
+ }
+ }.startOrbotAndListen(fragmentActivity);
return false;
} else {
@@ -213,40 +369,83 @@ public class OrbotHelper {
}
}
- public static boolean putOrbotInRequiredState(final int middleButton,
- final Runnable middleButtonRunnable,
- Preferences.ProxyPrefs proxyPrefs,
+ public static boolean putOrbotInRequiredState(DialogActions dialogActions,
FragmentActivity fragmentActivity) {
- Runnable emptyRunnable = new Runnable() {
+ return putOrbotInRequiredState(R.string.orbot_ignore_tor,
+ dialogActions,
+ Preferences.getPreferences(fragmentActivity).getProxyPrefs(),
+ fragmentActivity);
+ }
+
+ /**
+ * will attempt a silent start, which if disabled will fallback to the
+ * {@link #requestShowOrbotStart(Activity) requestShowOrbotStart} method, which returns the
+ * result in {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
+ * with a {@code requestCode} of {@code START_TOR_RESULT}, which will have to be implemented by
+ * activities wishing to respond to a change in Orbot state.
+ */
+ public static void bestPossibleOrbotStart(final DialogActions dialogActions,
+ final Activity activity) {
+ new SilentStartManager() {
+
@Override
- public void run() {
+ protected void onOrbotStarted() {
+ dialogActions.onOrbotStarted();
+ }
+ @Override
+ protected void onSilentStartDisabled() {
+ requestShowOrbotStart(activity);
}
- };
- return putOrbotInRequiredState(middleButton, middleButtonRunnable, emptyRunnable,
- proxyPrefs, fragmentActivity);
+ }.startOrbotAndListen(activity);
}
/**
- * generates a standard Orbot install/enable dialog if necessary, based on proxy settings in
- * preferences
- *
- * @param ignoreTorRunnable run when the "Ignore Tor" button is pressed
- * @param fragmentActivity used to start the activ
- * @return
+ * base class for listening to silent orbot starts. Also handles display of progress dialog.
*/
- public static boolean putOrbotInRequiredState(Runnable ignoreTorRunnable,
- FragmentActivity fragmentActivity) {
- return putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTorRunnable,
- Preferences.getPreferences(fragmentActivity).getProxyPrefs(), fragmentActivity);
- }
+ private static abstract class SilentStartManager {
- public static boolean putOrbotInRequiredState(Runnable ignoreTorRunnable,
- Runnable dismissDialogRunnable,
- FragmentActivity fragmentActivity) {
- return putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTorRunnable,
- dismissDialogRunnable,
- Preferences.getPreferences(fragmentActivity).getProxyPrefs(),
- fragmentActivity);
+ private ProgressDialog mProgressDialog;
+
+ public void startOrbotAndListen(Context context) {
+ mProgressDialog = new ProgressDialog(ThemeChanger.getDialogThemeWrapper(context));
+ mProgressDialog.setMessage(context.getString(R.string.progress_starting_orbot));
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.show();
+
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getStringExtra(OrbotHelper.EXTRA_STATUS)) {
+ case OrbotHelper.STATUS_ON:
+ context.unregisterReceiver(this);
+ // generally Orbot starts working a little after this status is received
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mProgressDialog.dismiss();
+ onOrbotStarted();
+ }
+ }, 1000);
+ break;
+ case OrbotHelper.STATUS_STARTS_DISABLED:
+ context.unregisterReceiver(this);
+ mProgressDialog.dismiss();
+ onSilentStartDisabled();
+ break;
+
+ }
+ Log.d(Constants.TAG, "Orbot silent start broadcast: " +
+ intent.getStringExtra(OrbotHelper.EXTRA_STATUS));
+ }
+ };
+ context.registerReceiver(receiver, new IntentFilter(OrbotHelper.ACTION_STATUS));
+
+ requestStartTor(context);
+ }
+
+ protected abstract void onOrbotStarted();
+
+ protected abstract void onSilentStartDisabled();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java
index d11e80f7a..2638f8cd5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java
@@ -49,7 +49,8 @@
package org.sufficientlysecure.keychain.util.orbot;
-import org.sufficientlysecure.keychain.Constants;
+import android.content.Context;
+
import org.sufficientlysecure.keychain.util.Log;
import java.io.BufferedReader;
@@ -62,24 +63,27 @@ import java.util.StringTokenizer;
* This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/
*/
public class TorServiceUtils {
- // various console cmds
+
+ private final static String TAG = "TorUtils";
+
public final static String SHELL_CMD_PS = "ps";
public final static String SHELL_CMD_PIDOF = "pidof";
- public static int findProcessId(String command) {
+ public static int findProcessId(Context context) {
+ String dataPath = context.getFilesDir().getParentFile().getParentFile().getAbsolutePath();
+ String command = dataPath + "/" + OrbotHelper.ORBOT_PACKAGE_NAME + "/app_bin/tor";
int procId = -1;
try {
procId = findProcessIdWithPidOf(command);
- if (procId == -1) {
+ if (procId == -1)
procId = findProcessIdWithPS(command);
- }
} catch (Exception e) {
try {
procId = findProcessIdWithPS(command);
} catch (Exception e2) {
- Log.e(Constants.TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2);
+ Log.e(TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2);
}
}
@@ -90,7 +94,9 @@ public class TorServiceUtils {
public static int findProcessIdWithPidOf(String command) throws Exception {
int procId = -1;
+
Runtime r = Runtime.getRuntime();
+
Process procPs;
String baseName = new File(command).getName();
@@ -115,18 +121,23 @@ public class TorServiceUtils {
}
return procId;
+
}
// use 'ps' command
public static int findProcessIdWithPS(String command) throws Exception {
+
int procId = -1;
+
Runtime r = Runtime.getRuntime();
+
Process procPs;
procPs = r.exec(SHELL_CMD_PS);
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
String line;
+
while ((line = reader.readLine()) != null) {
if (line.contains(' ' + command)) {
@@ -140,5 +151,6 @@ public class TorServiceUtils {
}
return procId;
+
}
-}
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index 0c394bda7..ddf10c988 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -421,6 +421,8 @@
<string name="progress_verifying_keyserver_url">"verifying keyserver…"</string>
+ <string name="progress_starting_orbot">"Starting Orbot…"</string>
+
<!-- action strings -->
<string name="hint_cloud_search_hint">"Search via Name, Email…"</string>