[tor-commits] [orbot/master] Issue #324: Implemented CustomBridgesActivity analogous to Onion Browser iOS.

n8fr8 at torproject.org n8fr8 at torproject.org
Mon Aug 24 21:02:51 UTC 2020


commit 718bb4216527a6af649d4d821b94f67be0d91ebb
Author: Benjamin Erhart <berhart at 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 at 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 at 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>





More information about the tor-commits mailing list