commit 718bb4216527a6af649d4d821b94f67be0d91ebb Author: Benjamin Erhart berhart@netzarchitekten.com Date: Mon May 4 15:04:57 2020 +0200
Issue #324: Implemented CustomBridgesActivity analogous to Onion Browser iOS. --- app/src/main/AndroidManifest.xml | 1 + .../java/org/torproject/android/MainConstants.java | 4 +- .../ui/onboarding/BridgeWizardActivity.java | 90 ++------- .../ui/onboarding/CustomBridgesActivity.java | 219 +++++++++++++++++++++ .../android/ui/onboarding/MoatActivity.java | 2 +- .../main/res/layout/activity_custom_bridges.xml | 100 ++++++++++ app/src/main/res/layout/content_bridge_wizard.xml | 20 +- app/src/main/res/values/strings.xml | 11 +- 8 files changed, 365 insertions(+), 82 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 011b78c7..a3fbec82 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -108,6 +108,7 @@ <activity android:name=".ui.onboarding.OnboardingActivity" /> <activity android:name=".ui.onboarding.BridgeWizardActivity" /> <activity android:name=".ui.onboarding.MoatActivity" /> + <activity android:name=".ui.onboarding.CustomBridgesActivity" />
<provider android:name=".ui.hiddenservices.providers.HSContentProvider" diff --git a/app/src/main/java/org/torproject/android/MainConstants.java b/app/src/main/java/org/torproject/android/MainConstants.java index f7f20cbe..06e99861 100644 --- a/app/src/main/java/org/torproject/android/MainConstants.java +++ b/app/src/main/java/org/torproject/android/MainConstants.java @@ -8,7 +8,9 @@ public interface MainConstants { //path to check Tor against String URL_TOR_CHECK = "https://check.torproject.org";
- String URL_TOR_BRIDGES = "https://bridges.torproject.org/bridges?transport="; + String URL_TOR_BRIDGES = "https://bridges.torproject.org/bridges"; + + String EMAIL_TOR_BRIDGES = "bridges@torproject.org";
int RESULT_CLOSE_ALL = 0;
diff --git a/app/src/main/java/org/torproject/android/ui/onboarding/BridgeWizardActivity.java b/app/src/main/java/org/torproject/android/ui/onboarding/BridgeWizardActivity.java index 6b439468..7f7561ba 100644 --- a/app/src/main/java/org/torproject/android/ui/onboarding/BridgeWizardActivity.java +++ b/app/src/main/java/org/torproject/android/ui/onboarding/BridgeWizardActivity.java @@ -1,14 +1,11 @@ package org.torproject.android.ui.onboarding;
-import android.app.AlertDialog; -import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; -import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.text.TextUtils; +import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.RadioButton; @@ -28,8 +25,6 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress;
-import static org.torproject.android.MainConstants.URL_TOR_BRIDGES; - public class BridgeWizardActivity extends AppCompatActivity {
private static int MOAT_REQUEST_CODE = 666; @@ -38,8 +33,7 @@ public class BridgeWizardActivity extends AppCompatActivity { private RadioButton mBtDirect; private RadioButton mBtObfs4; private RadioButton mBtMeek; - private RadioButton mBtNew; - private RadioButton mBtMoat; + private RadioButton mBtCustom;
@Override @@ -59,6 +53,14 @@ public class BridgeWizardActivity extends AppCompatActivity {
setTitle(getString(R.string.bridges));
+ findViewById(R.id.btnMoat).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivityForResult(new Intent(BridgeWizardActivity.this, MoatActivity.class), + MOAT_REQUEST_CODE); + } + }); + mBtDirect = findViewById(R.id.btnBridgesDirect); mBtDirect.setOnClickListener(new View.OnClickListener() { @Override @@ -91,22 +93,18 @@ public class BridgeWizardActivity extends AppCompatActivity { });
- mBtNew = findViewById(R.id.btnBridgesNew); - mBtNew.setOnClickListener(new View.OnClickListener() { + mBtCustom = findViewById(R.id.btnCustomBridges); + mBtCustom.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { - showGetBridgePrompt(); + public void onClick(View view) { + startActivity(new Intent(BridgeWizardActivity.this, CustomBridgesActivity.class)); } }); + }
- mBtMoat = findViewById(R.id.btnMoat); - mBtMoat.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivityForResult(new Intent(BridgeWizardActivity.this, MoatActivity.class), - MOAT_REQUEST_CODE); - } - }); + @Override + protected void onResume() { + super.onResume();
evaluateBridgeListState(); } @@ -144,49 +142,6 @@ public class BridgeWizardActivity extends AppCompatActivity { } }
- private void showGetBridgePrompt() { - new AlertDialog.Builder(this) - .setTitle(R.string.bridge_mode) - .setMessage(R.string.you_must_get_a_bridge_address_by_email_web_or_from_a_friend_once_you_have_this_address_please_paste_it_into_the_bridges_preference_in_orbot_s_setting_and_restart_) - .setNegativeButton(R.string.btn_cancel, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - //do nothing - } - }) - .setNeutralButton(R.string.get_bridges_email, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - sendGetBridgeEmail(); - } - - }) - .setPositiveButton(R.string.get_bridges_web, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - openBrowser(URL_TOR_BRIDGES); - } - }).show(); - } - - private void sendGetBridgeEmail() { - String email = "bridges@torproject.org"; - Uri emailUri = Uri.parse("mailto:" + email); - Intent emailIntent = new Intent(Intent.ACTION_SENDTO, emailUri); - emailIntent.putExtra(Intent.EXTRA_SUBJECT, "get transport"); - emailIntent.putExtra(Intent.EXTRA_TEXT, "get transport"); - startActivity(Intent.createChooser(emailIntent, getString(R.string.send_email))); - } - - - /* - * Launch the system activity for Uri viewing with the provided url - */ - @SuppressWarnings("SameParameterValue") - private void openBrowser(final String browserLaunchUrl) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(browserLaunchUrl))); - } -
private void testBridgeConnection() { if (TextUtils.isEmpty(Prefs.getBridgesList()) || (!Prefs.bridgesEnabled())) { @@ -258,6 +213,8 @@ public class BridgeWizardActivity extends AppCompatActivity { }
private void evaluateBridgeListState() { + Log.d(getClass().getSimpleName(), String.format("bridgesEnabled=%b, bridgesList=%s", Prefs.bridgesEnabled(), Prefs.getBridgesList())); + if (!Prefs.bridgesEnabled()) { mBtDirect.setChecked(true); } @@ -268,12 +225,7 @@ public class BridgeWizardActivity extends AppCompatActivity { mBtObfs4.setChecked(true); } else { - mBtDirect.setChecked(false); - mBtMeek.setChecked(false); - mBtObfs4.setChecked(false); + mBtCustom.setChecked(true); } - - mBtNew.setChecked(false); - mBtMoat.setChecked(false); } } diff --git a/app/src/main/java/org/torproject/android/ui/onboarding/CustomBridgesActivity.java b/app/src/main/java/org/torproject/android/ui/onboarding/CustomBridgesActivity.java new file mode 100644 index 00000000..0aa0c5ee --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/onboarding/CustomBridgesActivity.java @@ -0,0 +1,219 @@ +/* Copyright (c) 2020, Benjamin Erhart, Orbot / The Guardian Project - https://guardianproject.info */ +/* See LICENSE for licensing information */ +package org.torproject.android.ui.onboarding; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +import org.json.JSONArray; +import org.torproject.android.R; +import org.torproject.android.service.OrbotService; +import org.torproject.android.service.TorServiceConstants; +import org.torproject.android.service.util.Prefs; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +import static org.torproject.android.MainConstants.EMAIL_TOR_BRIDGES; +import static org.torproject.android.MainConstants.URL_TOR_BRIDGES; + +public class CustomBridgesActivity extends AppCompatActivity implements View.OnClickListener, TextWatcher { + + private EditText mEtPastedBridges; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_custom_bridges); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + + setTitle(getString(R.string.use_custom_bridges)); + + ((TextView) findViewById(R.id.tvDescription)).setText(getString(R.string.in_a_browser, URL_TOR_BRIDGES)); + + findViewById(R.id.btCopyUrl).setOnClickListener(this); + + String bridges = Prefs.getBridgesList().trim(); + if (!Prefs.bridgesEnabled() || bridges.equals("obfs4") || bridges.equals("meek")) { + bridges = null; + } + + mEtPastedBridges = findViewById(R.id.etPastedBridges); + mEtPastedBridges.setText(bridges); + mEtPastedBridges.addTextChangedListener(this); + + findViewById(R.id.btScanQr).setOnClickListener(this); + + findViewById(R.id.btShareQr).setOnClickListener(this); + + findViewById(R.id.btEmail).setOnClickListener(this); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onClick(View view) { + IntentIntegrator integrator = new IntentIntegrator(this); + + switch (view.getId()) { + case R.id.btCopyUrl: + ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + + if (clipboard != null) { + clipboard.setPrimaryClip(ClipData.newPlainText(URL_TOR_BRIDGES, URL_TOR_BRIDGES)); + + Toast.makeText(this, R.string.done, Toast.LENGTH_LONG).show(); + } + + break; + + case R.id.btScanQr: + integrator.initiateScan(); + + break; + + case R.id.btShareQr: + String bridges = Prefs.getBridgesList(); + + if (!TextUtils.isEmpty(bridges)) { + try { + bridges = "bridge://" + URLEncoder.encode(bridges, "UTF-8"); + + integrator.shareText(bridges); + + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + break; + + case R.id.btEmail: + Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + EMAIL_TOR_BRIDGES)); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, "get transport"); + emailIntent.putExtra(Intent.EXTRA_TEXT, "get transport"); + startActivity(Intent.createChooser(emailIntent, getString(R.string.send_email))); + + break; + } + } + + @Override + protected void onActivityResult(int request, int response, Intent data) { + super.onActivityResult(request, response, data); + + IntentResult scanResult = IntentIntegrator.parseActivityResult(request, response, data); + + if (scanResult != null) { + String results = scanResult.getContents(); + + if (!TextUtils.isEmpty(results)) { + try { + + int urlIdx = results.indexOf("://"); + + if (urlIdx != -1) { + results = URLDecoder.decode(results, "UTF-8"); + results = results.substring(urlIdx + 3); + + setNewBridges(results); + } + else { + JSONArray bridgeJson = new JSONArray(results); + StringBuilder bridgeLines = new StringBuilder(); + + for (int i = 0; i < bridgeJson.length(); i++) { + String bridgeLine = bridgeJson.getString(i); + bridgeLines.append(bridgeLine).append("\n"); + } + + setNewBridges(bridgeLines.toString()); + } + } + catch (Exception e) { + Log.e(getClass().getSimpleName(), "unsupported", e); + } + } + + setResult(RESULT_OK); + } + } + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + // Ignored. + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + // Ignored. + } + + @Override + public void afterTextChanged(Editable editable) { + setNewBridges(editable.toString(), false); + } + + private void setNewBridges(String newBridgeValue) { + setNewBridges(newBridgeValue, true); + } + + private void setNewBridges(String bridges, boolean updateEditText) { + if (bridges != null) { + bridges = bridges.trim(); + + if (TextUtils.isEmpty(bridges)) { + bridges = null; + } + } + + if (updateEditText) { + mEtPastedBridges.setText(bridges); + } + + Prefs.setBridgesList(bridges); + Prefs.putBridgesEnabled(bridges != null); + + Intent intent = new Intent(this, OrbotService.class); + intent.setAction(TorServiceConstants.CMD_SIGNAL_HUP); + startService(intent); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java b/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java index 89a20eec..df390a2f 100644 --- a/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java +++ b/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java @@ -405,7 +405,7 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe
Log.d(MoatActivity.class.getSimpleName(), "Set up Volley queue. host=" + host + ", port=" + port);
- mQueue = Volley.newRequestQueue(MoatActivity.this, new ProxiedHurlStack(host, port)); + mQueue = Volley.newRequestQueue(this, new ProxiedHurlStack(host, port));
sendIntentToService(TorServiceConstants.CMD_SIGNAL_HUP);
diff --git a/app/src/main/res/layout/activity_custom_bridges.xml b/app/src/main/res/layout/activity_custom_bridges.xml new file mode 100644 index 00000000..8a7b2998 --- /dev/null +++ b/app/src/main/res/layout/activity_custom_bridges.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/dark_purple" + tools:context=".ui.onboarding.CustomBridgesActivity"> + + <com.google.android.material.appbar.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="@style/DefaultTheme.AppBarOverlay"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + app:popupTheme="@style/DefaultTheme.PopupOverlay" /> + + </com.google.android.material.appbar.AppBarLayout> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:id="@+id/tvDescription" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/in_a_browser" /> + + <Button + android:id="@+id/btCopyUrl" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/copy_address_to_clipboard" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/paste_bridges" /> + + <EditText + android:id="@+id/etPastedBridges" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:lines="5" + android:inputType="textMultiLine|textNoSuggestions" + tools:ignore="Autofill,LabelFor" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/use_qr_code" /> + + <Button + android:id="@+id/btScanQr" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/menu_scan" /> + + <Button + android:id="@+id/btShareQr" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/menu_share_bridge" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/get_bridges_email" /> + + <Button + android:id="@+id/btEmail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/get_bridges_email" /> + + </LinearLayout> + + </ScrollView> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/content_bridge_wizard.xml b/app/src/main/res/layout/content_bridge_wizard.xml index ce8b6e6c..d8e20d82 100644 --- a/app/src/main/res/layout/content_bridge_wizard.xml +++ b/app/src/main/res/layout/content_bridge_wizard.xml @@ -19,8 +19,15 @@ android:textSize="16sp" android:textStyle="bold" />
+ <Button + android:id="@+id/btnMoat" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:text="@string/request_bridges_from_torproject" /> + <RadioGroup - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
@@ -46,18 +53,11 @@ android:text="@string/bridge_cloud" />
<RadioButton - android:id="@+id/btnBridgesNew" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="12dp" - android:text="@string/bridges_get_new" /> - - <RadioButton - android:id="@+id/btnMoat" + android:id="@+id/btnCustomBridges" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="12dp" - android:text="@string/bridges_get_new_moat"/> + android:text="@string/custom_bridges"/>
</RadioGroup>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 072de5b5..d7139115 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -249,7 +249,6 @@ <string name="bridge_community">Connect through community servers</string> <string name="bridge_cloud">Connect through cloud servers</string> <string name="bridges_get_new">Request New Bridges via Email...</string> - <string name="bridges_get_new_moat">Request New Bridges Directly...</string>
<string name="trouble_connecting">Trouble connecting?</string> <string name="full_device_vpn">Full Device VPN</string> @@ -268,4 +267,14 @@ <string name="solve_captcha_instruction">Solve the CAPTCHA to request bridges.</string> <string name="captcha">Captcha</string> <string name="enter_characters_from_image">Enter characters from image</string> + + <!-- BridgeWizardActivity --> + <string name="request_bridges_from_torproject">Request Bridges from torproject.org</string> + <string name="custom_bridges">Custom Bridges</string> + + <!-- CustomBridgesActivity --> + <string name="use_custom_bridges">Use Custom Bridges</string> + <string name="in_a_browser">In a browser, visit %s and tap "Get Bridges" > "Just Give Me Bridges!"</string> + <string name="paste_bridges">Paste Bridges</string> + <string name="use_qr_code">Use QR Code</string> </resources>
tor-commits@lists.torproject.org