[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [orbot/master] Added first stab at a MoatActivity. Doesn't change bridges automatically, yet. Also, Volley needs to be proxied.
commit e7cfe4651d6874f35579f724f22e83c78cec04af
Author: Benjamin Erhart <berhart@xxxxxxxxxxxxxxxxxxx>
Date: Fri Apr 17 13:43:05 2020 +0200
Added first stab at a MoatActivity. Doesn't change bridges automatically, yet. Also, Volley needs to be proxied.
---
app/build.gradle | 11 +-
app/src/main/AndroidManifest.xml | 139 ++++++------
.../ui/onboarding/BridgeWizardActivity.java | 48 +++--
.../android/ui/onboarding/MoatActivity.java | 233 +++++++++++++++++++++
app/src/main/res/layout/activity_moat.xml | 53 +++++
app/src/main/res/layout/content_bridge_wizard.xml | 9 +-
app/src/main/res/menu/moat.xml | 28 +++
app/src/main/res/values/strings.xml | 9 +-
8 files changed, 443 insertions(+), 87 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 20707251..e6cf6de7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -24,7 +24,7 @@ def getVersionName = { ->
}
android {
- signingConfigs {
+ signingConfigs {
release {
if (keystorePropertiesFile.canRead()) {
keyAlias keystoreProperties['keyAlias']
@@ -118,15 +118,17 @@ android {
dependencies {
implementation project(':orbotservice')
- implementation 'com.google.android.material:material:1.0.0'
+ implementation 'com.google.android.material:material:1.1.0'
implementation 'pl.bclogic:pulsator4droid:1.0.3'
implementation 'com.github.apl-devs:appintro:v4.2.2'
- implementation 'com.github.javiersantos:AppUpdater:2.6.4'
+ implementation 'com.github.javiersantos:AppUpdater:2.7'
androidTestImplementation "tools.fastlane:screengrab:1.2.0"
+ implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
+ implementation 'com.android.volley:volley:1.1.1'
}
// Map for the version code that gives each ABI a value.
-ext.abiCodes = ['armeabi-v7a':'1', 'arm64-v8a':'2', 'mips':'3', 'x86':'4', 'x86_64':'5']
+ext.abiCodes = ['armeabi-v7a': '1', 'arm64-v8a': '2', 'mips': '3', 'x86': '4', 'x86_64': '5']
import com.android.build.OutputFile
@@ -141,4 +143,3 @@ android.applicationVariants.all { variant ->
}
}
}
-
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1d7a9f16..011b78c7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,14 @@
package="org.torproject.android"
android:installLocation="internalOnly">
+ <!--
+ Some Chromebooks don't support touch. Although not essential,
+ it's a good idea to explicitly include this declaration.
+ -->
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -11,10 +19,6 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
- <!-- Some Chromebooks don't support touch. Although not essential,
- it's a good idea to explicitly include this declaration. -->
- <uses-feature android:name="android.hardware.touchscreen"
- android:required="false" />
<application
android:name=".OrbotApp"
@@ -26,8 +30,8 @@
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/DefaultTheme"
- tools:replace="android:allowBackup"
- >
+ tools:replace="android:allowBackup">
+
<activity
android:name=".OrbotMainActivity"
android:excludeFromRecents="false"
@@ -46,18 +50,17 @@
<data android:scheme="bridge" />
</intent-filter>
<intent-filter>
- <category android:name="android.intent.category.DEFAULT" />
-
<action android:name="org.torproject.android.REQUEST_HS_PORT" />
+
+ <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
- <category android:name="android.intent.category.DEFAULT" />
-
<action android:name="org.torproject.android.START_TOR" />
+
+ <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
- </activity>
+ </activity> <!-- This is for ensuring the background service still runs when/if the app is swiped away -->
- <!-- This is for ensuring the background service still runs when/if the app is swiped away -->
<activity
android:name=".service.util.DummyActivity"
android:allowTaskReparenting="true"
@@ -69,39 +72,72 @@
android:noHistory="true"
android:stateNotNeeded="true"
android:theme="@android:style/Theme.Translucent" />
+
<activity
android:name=".ui.VPNEnableActivity"
android:exported="false"
android:label="@string/app_name" />
+
<activity
android:name=".settings.SettingsPreferences"
android:label="@string/app_name" />
+
<activity
android:name=".ui.AppManagerActivity"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" />
- <service
- android:name=".service.OrbotService"
- android:enabled="true"
- android:permission="android.permission.BIND_VPN_SERVICE"
- android:stopWithTask="false"></service>
- <service
- android:name=".service.vpn.TorVpnService"
- android:enabled="true"
- android:permission="android.permission.BIND_VPN_SERVICE">
- <intent-filter>
- <action android:name="android.net.VpnService" />
- </intent-filter>
- </service>
+ <activity
+ android:name=".ui.hiddenservices.HiddenServicesActivity"
+ android:label="@string/title_activity_hidden_services"
+ android:theme="@style/DefaultTheme">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".OrbotMainActivity" />
+ </activity>
+
+ <activity
+ android:name=".ui.hiddenservices.ClientCookiesActivity"
+ android:label="@string/client_cookies"
+ android:theme="@style/DefaultTheme">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".OrbotMainActivity" />
+ </activity>
+
+ <activity android:name=".ui.onboarding.OnboardingActivity" />
+ <activity android:name=".ui.onboarding.BridgeWizardActivity" />
+ <activity android:name=".ui.onboarding.MoatActivity" />
+
+ <provider
+ android:name=".ui.hiddenservices.providers.HSContentProvider"
+ android:authorities="org.torproject.android.ui.hiddenservices.providers"
+ android:exported="false" />
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="org.torproject.android.ui.hiddenservices.storage"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/hidden_services_paths" />
+ </provider>
+
+ <provider
+ android:name=".ui.hiddenservices.providers.CookieContentProvider"
+ android:authorities="org.torproject.android.ui.hiddenservices.providers.cookie"
+ android:exported="false" />
<receiver
android:name=".service.StartTorReceiver"
- android:exported="true">
+ android:exported="true"
+ tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="org.torproject.android.intent.action.START" />
</intent-filter>
</receiver>
+
<receiver
android:name=".OnBootReceiver"
android:enabled="true"
@@ -123,45 +159,20 @@
</intent-filter>
</receiver>
- <activity
- android:name=".ui.hiddenservices.HiddenServicesActivity"
- android:label="@string/title_activity_hidden_services"
- android:theme="@style/DefaultTheme">
- <meta-data
- android:name="android.support.PARENT_ACTIVITY"
- android:value=".OrbotMainActivity" />
- </activity>
-
- <provider
- android:name=".ui.hiddenservices.providers.HSContentProvider"
- android:authorities="org.torproject.android.ui.hiddenservices.providers"
- android:exported="false" />
- <provider
- android:name="androidx.core.content.FileProvider"
- android:authorities="org.torproject.android.ui.hiddenservices.storage"
- android:exported="false"
- android:grantUriPermissions="true">
- <meta-data
- android:name="android.support.FILE_PROVIDER_PATHS"
- android:resource="@xml/hidden_services_paths" />
- </provider>
-
- <activity
- android:name=".ui.hiddenservices.ClientCookiesActivity"
- android:label="@string/client_cookies"
- android:theme="@style/DefaultTheme">
- <meta-data
- android:name="android.support.PARENT_ACTIVITY"
- android:value=".OrbotMainActivity" />
- </activity>
-
- <activity android:name=".ui.onboarding.OnboardingActivity"/>
- <activity android:name=".ui.onboarding.BridgeWizardActivity"/>
+ <service
+ android:name=".service.OrbotService"
+ android:enabled="true"
+ android:permission="android.permission.BIND_VPN_SERVICE"
+ android:stopWithTask="false" />
- <provider
- android:name=".ui.hiddenservices.providers.CookieContentProvider"
- android:authorities="org.torproject.android.ui.hiddenservices.providers.cookie"
- android:exported="false" />
+ <service
+ android:name=".service.vpn.TorVpnService"
+ android:enabled="true"
+ android:permission="android.permission.BIND_VPN_SERVICE">
+ <intent-filter>
+ <action android:name="android.net.VpnService" />
+ </intent-filter>
+ </service>
</application>
</manifest>
\ No newline at end of file
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 6e54e103..5ffe79e5 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
@@ -8,13 +8,16 @@ import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.RadioButton;
import android.widget.TextView;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
import org.torproject.android.R;
import org.torproject.android.service.util.Prefs;
import org.torproject.android.settings.LocaleHelper;
@@ -36,7 +39,11 @@ public class BridgeWizardActivity extends AppCompatActivity {
setContentView(R.layout.activity_bridge_wizard);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
tvStatus = findViewById(R.id.lbl_bridge_test_status);
tvStatus.setVisibility(View.GONE);
@@ -83,6 +90,14 @@ public class BridgeWizardActivity extends AppCompatActivity {
}
});
+ RadioButton btnMoat = findViewById(R.id.btnMoat);
+ btnMoat.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startActivity(new Intent(BridgeWizardActivity.this, MoatActivity.class));
+ }
+ });
+
if (!Prefs.bridgesEnabled())
btnDirect.setChecked(true);
else if (Prefs.getBridgesList().equals("meek"))
@@ -128,7 +143,7 @@ public class BridgeWizardActivity extends AppCompatActivity {
.setPositiveButton(R.string.get_bridges_web, new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- openBrowser(URL_TOR_BRIDGES, true);
+ openBrowser(URL_TOR_BRIDGES);
}
}).show();
}
@@ -146,7 +161,8 @@ public class BridgeWizardActivity extends AppCompatActivity {
/*
* Launch the system activity for Uri viewing with the provided url
*/
- private void openBrowser(final String browserLaunchUrl, boolean forceExternal) {
+ @SuppressWarnings("SameParameterValue")
+ private void openBrowser(final String browserLaunchUrl) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(browserLaunchUrl)));
}
@@ -174,18 +190,17 @@ public class BridgeWizardActivity extends AppCompatActivity {
@Override
protected Boolean doInBackground(String... host) {
// Background Code
- boolean result = false;
-
for (int i = 0; i < host.length; i++) {
String testHost = host[i];
i++; //move to the port
int testPort = Integer.parseInt(host[i]);
- result = isHostReachable(testHost, testPort, 10000);
- if (result)
- return result;
+
+ if (isHostReachable(testHost, testPort, 10000)) {
+ return true;
+ }
}
- return result;
+ return false;
}
@Override
@@ -201,22 +216,23 @@ public class BridgeWizardActivity extends AppCompatActivity {
}
}
+ @SuppressWarnings("SameParameterValue")
private static boolean isHostReachable(String serverAddress, int serverTCPport, int timeoutMS) {
boolean connected = false;
- Socket socket;
+
try {
- socket = new Socket();
+ Socket socket = new Socket();
SocketAddress socketAddress = new InetSocketAddress(serverAddress, serverTCPport);
socket.connect(socketAddress, timeoutMS);
if (socket.isConnected()) {
connected = true;
socket.close();
}
- } catch (IOException e) {
+ }
+ catch (IOException e) {
e.printStackTrace();
- } finally {
- socket = null;
}
+
return connected;
}
}
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
new file mode 100644
index 00000000..e5ea5459
--- /dev/null
+++ b/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java
@@ -0,0 +1,233 @@
+package org.torproject.android.ui.onboarding;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.util.Base64;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.JsonObjectRequest;
+import com.android.volley.toolbox.Volley;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.torproject.android.R;
+import org.torproject.android.service.util.Prefs;
+
+/**
+ Implements the MOAT protocol: Fetches OBFS4 bridges via Meek Azure.
+
+ The bare minimum of the communication is implemented. E.g. no check, if OBFS4 is possible or which
+ protocol version the server wants to speak. The first should be always good, as OBFS4 is the most widely
+ supported bridge type, the latter should be the same as we requested (0.1.0) anyway.
+
+ API description:
+ https://github.com/NullHypothesis/bridgedb#accessing-the-moat-interface
+ */
+public class MoatActivity extends AppCompatActivity implements View.OnClickListener {
+
+ private static String moatBaseUrl = "https://bridges.torproject.org/moat";
+
+ private ImageView mCaptchaIv;
+ private EditText mSolutionEt;
+
+ private String mChallenge;
+
+ private RequestQueue mQueue;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_moat);
+
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ setTitle(getString(R.string.request_bridges));
+
+ mCaptchaIv = findViewById(R.id.captchaIv);
+ mSolutionEt = findViewById(R.id.solutionEt);
+
+ findViewById(R.id.requestBt).setOnClickListener(this);
+
+ mQueue = Volley.newRequestQueue(this);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+
+ getMenuInflater().inflate(R.menu.moat, menu);
+
+ return true;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // Set to Meek bridge.
+ Prefs.setBridgesList("meek");
+ Prefs.putBridgesEnabled(true);
+
+ fetchCaptcha();
+ }
+
+ @Override
+ public void onClick(View view) {
+ Log.d(MoatActivity.class.toString(), "Request Bridge!");
+
+ requestBridges(mSolutionEt.getText().toString());
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.menu_refresh) {
+ fetchCaptcha();
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void fetchCaptcha() {
+ JsonObjectRequest request = buildRequest("fetch",
+ "\"type\": \"client-transports\", \"supported\": [\"obfs4\"]",
+ new Response.Listener<JSONObject>() {
+ @Override
+ public void onResponse(JSONObject response) {
+ try {
+ JSONObject data = response.getJSONArray("data").getJSONObject(0);
+ mChallenge = data.getString("challenge");
+
+ byte[] image = Base64.decode(data.getString("image"), Base64.DEFAULT);
+ mCaptchaIv.setImageBitmap(BitmapFactory.decodeByteArray(image, 0, image.length));
+
+ } catch (JSONException e) {
+ Log.d(MoatActivity.class.toString(), "Error decoding answer.");
+
+ new AlertDialog.Builder(MoatActivity.this)
+ .setTitle(R.string.error)
+ .setMessage(e.getLocalizedMessage())
+ .setNegativeButton(R.string.btn_cancel, new Dialog.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //Do nothing.
+ }
+ })
+ .show();
+ }
+ }
+ });
+
+ if (request != null) {
+ mQueue.add(request);
+ }
+ }
+
+ private void requestBridges(String solution) {
+ JsonObjectRequest request = buildRequest("check",
+ "\"id\": \"2\", \"type\": \"moat-solution\", \"transport\": \"obfs4\", \"challenge\": \""
+ + mChallenge + "\", \"solution\": \"" + solution + "\", \"qrcode\": \"false\"",
+ new Response.Listener<JSONObject>() {
+ @Override
+ public void onResponse(JSONObject response) {
+ try {
+ JSONArray bridges = response.getJSONArray("data").getJSONObject(0).getJSONArray("bridges");
+
+ Log.d(MoatActivity.class.toString(), "Bridges: " + bridges.toString());
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < bridges.length(); i++) {
+ sb.append(bridges.getString(i)).append("\n");
+ }
+
+ Prefs.setBridgesList(sb.toString());
+ Prefs.putBridgesEnabled(true);
+
+ MoatActivity.this.finish();
+
+ } catch (JSONException e) {
+ Log.d(MoatActivity.class.toString(), "Error decoding answer: " + response.toString());
+
+ new AlertDialog.Builder(MoatActivity.this)
+ .setTitle(R.string.error)
+ .setMessage(e.getLocalizedMessage())
+ .setNegativeButton(R.string.btn_cancel, new Dialog.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //Do nothing.
+ }
+ })
+ .show();
+ }
+ }
+ });
+
+ if (request != null) {
+ mQueue.add(request);
+ }
+ }
+
+ private JsonObjectRequest buildRequest(String endpoint, String payload, Response.Listener<JSONObject> listener) {
+ JSONObject requestBody;
+
+ try {
+ requestBody = new JSONObject("{\"data\": [{\"version\": \"0.1.0\", " + payload + "}]}");
+ } catch (JSONException e) {
+ return null;
+ }
+
+ Log.d(MoatActivity.class.toString(), "Request: " + requestBody.toString());
+
+ return new JsonObjectRequest(
+ Request.Method.POST,
+ moatBaseUrl + "/" + endpoint,
+ requestBody,
+ listener,
+ new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ Log.d(MoatActivity.class.toString(), "Error response.");
+
+ new AlertDialog.Builder(MoatActivity.this)
+ .setTitle(R.string.error)
+ .setMessage(error.getLocalizedMessage())
+ .setNegativeButton(R.string.btn_cancel, new Dialog.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //Do nothing.
+ }
+ })
+ .show();
+ }
+ }
+ ) {
+ public String getBodyContentType() {
+ return "application/vnd.api+json";
+ }
+ };
+ }
+}
diff --git a/app/src/main/res/layout/activity_moat.xml b/app/src/main/res/layout/activity_moat.xml
new file mode 100644
index 00000000..fe01873c
--- /dev/null
+++ b/app/src/main/res/layout/activity_moat.xml
@@ -0,0 +1,53 @@
+<?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:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@color/dark_purple"
+ tools:context=".ui.onboarding.MoatActivity">
+
+ <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>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="12dp"
+ android:text="@string/solve_captcha_instruction" />
+
+ <ImageView
+ android:id="@+id/captchaIv"
+ android:layout_width="match_parent"
+ android:layout_height="240dp"
+ android:contentDescription="@string/captcha"
+ tools:srcCompat="@tools:sample/backgrounds/scenic" />
+
+ <EditText
+ android:id="@+id/solutionEt"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:autofillHints=""
+ android:hint="@string/enter_characters_from_image"
+ android:ems="10"
+ android:inputType="textShortMessage|text" />
+
+ <Button
+ android:id="@+id/requestBt"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/request_bridges" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/content_bridge_wizard.xml b/app/src/main/res/layout/content_bridge_wizard.xml
index a5b7995b..1a1e98e0 100644
--- a/app/src/main/res/layout/content_bridge_wizard.xml
+++ b/app/src/main/res/layout/content_bridge_wizard.xml
@@ -19,7 +19,7 @@
android:textSize="16sp"
android:textStyle="bold" />
- <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+ <RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
@@ -52,6 +52,13 @@
android:layout_margin="12dp"
android:text="@string/bridges_get_new" />
+ <RadioButton
+ android:id="@+id/btnMoat"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="12dp"
+ android:text="@string/bridges_get_new"/>
+
</RadioGroup>
<TextView
diff --git a/app/src/main/res/menu/moat.xml b/app/src/main/res/menu/moat.xml
new file mode 100644
index 00000000..f32bbe11
--- /dev/null
+++ b/app/src/main/res/menu/moat.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:yourapp="http://schemas.android.com/apk/res-auto"
+ >
+ <item android:id="@+id/menu_refresh"
+ android:title="@string/refresh_captcha"
+ android:icon="@drawable/ic_refresh_white_24dp"
+ yourapp:showAsAction="always"
+ />
+</menu>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bff412a2..cc60ddfd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -124,7 +124,7 @@
<string name="newnym">You\'ve switched to a new Tor identity!</string>
<string name="pref_open_proxy_on_all_interfaces_title">Open Proxy on All Interfaces</string>
- <string name="pref_open_proxy_on_all_interfaces_summary">Allow Wi-Fi peers, tethered devices and anyone else who can connect to your IP, to access Tor</string>
+ <string name="pref_open_proxy_on_all_interfaces_summary">Allow Wi-Fi peers, tethered devices and anyone else who can connect to your IP, to access Tor</string>
<string name="no_network_connectivity_putting_tor_to_sleep_">No network connectivity. Putting Tor to sleepâ?¦</string>
<string name="network_connectivity_is_good_waking_tor_up_">Network connectivity is good. Waking Tor upâ?¦</string>
@@ -259,4 +259,11 @@
<string name="app_services">App services</string>
<string name="default_socks_http">SOCKS: - HTTP: -</string>
<string name="refresh_apps">Refresh Apps</string>
+
+ <!-- MoatActivity -->
+ <string name="request_bridges">Request Bridges</string>
+ <string name="refresh_captcha">Refresh CAPTCHA</string>
+ <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>
</resources>
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits