From 37864a9d42436081eb6132dc0c8b1e0868a15272 Mon Sep 17 00:00:00 2001 From: Adithya Abraham Philip Date: Thu, 6 Aug 2015 01:59:11 +0530 Subject: updated OrbotHelper with silent start --- .../keychain/ui/ImportKeysListFragment.java | 46 +-- .../keychain/ui/OrbotRequiredDialogActivity.java | 81 +++-- .../keychain/ui/SettingsKeyserverFragment.java | 9 +- .../keychain/ui/ViewKeyTrustFragment.java | 23 +- .../ui/dialog/AddEditKeyserverDialogFragment.java | 19 +- .../ui/dialog/OrbotStartDialogFragment.java | 73 +++-- .../ui/dialog/SupportInstallDialogFragment.java | 12 +- .../keychain/util/ParcelableProxy.java | 7 +- .../keychain/util/orbot/OrbotHelper.java | 339 ++++++++++++++++----- .../keychain/util/orbot/TorServiceUtils.java | 26 +- OpenKeychain/src/main/res/values/strings.xml | 2 + 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 implements ItemTouchHelperAdapter { - // to update the ViewHolder associated with first item, for when an item is deleted - private ViewHolder mFirstItem; - private final List mKeyservers; public KeyserverListAdapter(List 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 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 @@ "verifying keyserver…" + "Starting Orbot…" + "Search via Name, Email…" -- cgit v1.2.3