tor-commits
Threads by month
- ----- 2025 -----
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
August 2019
- 19 participants
- 2737 discussions

[tor-browser/tor-browser-68.1.0esr-9.0-1] Load torbutton in browser.xul on Android
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit 805ff051656dc22d11de026e01223352f39e7380
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Fri Aug 30 10:17:25 2019 -0400
Load torbutton in browser.xul on Android
---
mobile/android/chrome/content/browser.js | 4 ++++
mobile/android/chrome/content/browser.xul | 1 +
2 files changed, 5 insertions(+)
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index f8e9399f51c4..7fcee29f0c3c 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -243,6 +243,8 @@ var lazilyLoadedBrowserScripts = [
["CastingApps", "chrome://browser/content/CastingApps.js"],
["RemoteDebugger", "chrome://browser/content/RemoteDebugger.js"],
["gViewSourceUtils", "chrome://global/content/viewSourceUtils.js"],
+ ["torbuttonUtils", "chrome://torbutton/content/torbutton_utils.js"],
+ ["torbutton", "chrome://torbutton/content/torbutton.js"],
];
lazilyLoadedBrowserScripts.forEach(function(aScript) {
@@ -763,6 +765,8 @@ var BrowserApp = {
true
);
+ torbutton_init();
+
// We can't delay registering WebChannel listeners: if the first page is
// about:accounts, which can happen when starting the Firefox Account flow
// from the first run experience, or via the Firefox Account Status
diff --git a/mobile/android/chrome/content/browser.xul b/mobile/android/chrome/content/browser.xul
index 8072a7a1c6e5..71aa04df0ebf 100644
--- a/mobile/android/chrome/content/browser.xul
+++ b/mobile/android/chrome/content/browser.xul
@@ -4,6 +4,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
+<?xml-stylesheet href="chrome://torbutton/skin/torbutton.css" type="text/css"?>
<window id="main-window"
onload="BrowserApp.startup();"
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Bug 29238 - Prevent crash on Android after update
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit 8dbeac4a57547b530762a3a30cb7eea29df463d9
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Fri Mar 22 16:24:09 2019 +0000
Bug 29238 - Prevent crash on Android after update
---
mobile/android/base/AndroidManifest.xml.in | 8 --------
1 file changed, 8 deletions(-)
diff --git a/mobile/android/base/AndroidManifest.xml.in b/mobile/android/base/AndroidManifest.xml.in
index 228a7b6399b0..5f231e9ba83c 100644
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -492,14 +492,6 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- <receiver
- android:name="org.mozilla.gecko.PackageReplacedReceiver"
- android:exported="false">
- <intent-filter>
- <action android:name="android.intent.action.MY_PACKAGE_REPLACED"></action>
- </intent-filter>
- </receiver>
-
<service
android:name="org.mozilla.gecko.telemetry.TelemetryUploadService"
android:permission="android.permission.BIND_JOB_SERVICE"
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Pick up latest Torbutton fixes
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit 28bf38065aacbb6db2b19b024081aad6da7a1c51
Author: Georg Koppen <gk(a)torproject.org>
Date: Sat Aug 31 19:45:55 2019 +0000
Pick up latest Torbutton fixes
---
toolkit/torproject/torbutton | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/toolkit/torproject/torbutton b/toolkit/torproject/torbutton
index 605decfd4ddc..0efb110e9bc6 160000
--- a/toolkit/torproject/torbutton
+++ b/toolkit/torproject/torbutton
@@ -1 +1 @@
-Subproject commit 605decfd4ddc81eb37da17172f48f92fd7f7e451
+Subproject commit 0efb110e9bc65e3289c58d093c32a25877f61e0b
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Continue using non-adaptive icons and don't allow clear-text traffic
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit 144e2f64a8dbb3c84150d44fcdbab989f4aa4184
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Mon Jul 22 13:41:22 2019 +0000
Continue using non-adaptive icons and don't allow clear-text traffic
---
mobile/android/base/AndroidManifest.xml.in | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/mobile/android/base/AndroidManifest.xml.in b/mobile/android/base/AndroidManifest.xml.in
index 5f231e9ba83c..39c1899daa1f 100644
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -24,13 +24,11 @@
#include FennecManifest_permissions.xml.in
<application android:label="@string/moz_app_displayname"
- android:icon="@mipmap/ic_launcher"
- android:logo="@drawable/logo"
+ android:icon="@drawable/icon"
android:name="@MOZ_ANDROID_APPLICATION_CLASS@"
android:hardwareAccelerated="true"
android:supportsRtl="true"
android:allowBackup="false"
- android:usesCleartextTraffic="true"
>
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Bug 24920 - Only create Private tabs if browser.privatebrowsing.autostart is true
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit 576ff6dcffff7ad57c21c4bc469b52566d8da3dd
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Mon May 27 15:55:33 2019 +0000
Bug 24920 - Only create Private tabs if browser.privatebrowsing.autostart is true
---
.../base/java/org/mozilla/gecko/BrowserApp.java | 9 +++++++
.../base/java/org/mozilla/gecko/GeckoApp.java | 31 ++++++++++++++++++++++
.../android/base/java/org/mozilla/gecko/Tabs.java | 7 ++++-
.../java/org/mozilla/gecko/tabs/TabsPanel.java | 4 +++
mobile/android/chrome/content/browser.js | 4 +--
5 files changed, 52 insertions(+), 3 deletions(-)
diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
index da25e3b395be..c2a463ad6ed1 100644
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -3362,6 +3362,15 @@ public class BrowserApp extends GeckoApp
share.setActionProvider(provider);
+ // Change visibility of new_tab. This may be called before
+ // mOnlyPrivateTabs is set in GeckoApp::onCreate(). If the PrefHelper
+ // callback was not already called, then the visibility will be correctly set
+ // in the callback.
+ MenuItem newTabMenuItem = mMenu.findItem(R.id.new_tab);
+ if (newTabMenuItem != null) {
+ newTabMenuItem.setVisible(mOnlyPrivateTabs == false);
+ }
+
return true;
}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
index e01318dab422..9c66f56b0485 100644
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -165,6 +165,8 @@ public abstract class GeckoApp extends GeckoActivity
*/
public static final String PREFS_IS_FIRST_RUN = "telemetry-isFirstRun";
+ public static final String PREFS_PRIVATEBROWSING_AUTOSTART = "browser.privatebrowsing.autostart";
+
public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
/**
@@ -352,6 +354,8 @@ public abstract class GeckoApp extends GeckoActivity
private boolean mPrivateBrowsingSessionOutdated;
private static final int MAX_PRIVATE_TABS_UPDATE_WAIT_MSEC = 500;
+ protected boolean mOnlyPrivateTabs = false;
+
private volatile HealthRecorder mHealthRecorder;
private volatile Locale mLastLocale;
@@ -1164,6 +1168,28 @@ public abstract class GeckoApp extends GeckoActivity
"ToggleChrome:Show",
null);
+ PrefsHelper.getPref(PREFS_PRIVATEBROWSING_AUTOSTART,
+ new PrefsHelper.PrefHandlerBase() {
+ @Override public void prefValue(String pref, boolean value) {
+ if (pref != PREFS_PRIVATEBROWSING_AUTOSTART) {
+ return;
+ }
+
+ mOnlyPrivateTabs = value;
+
+ // Change visibility here in case mMenu is initialized. If it is not initialized,
+ // then the visibility is set in BrowserApp::onCreateOptionsMenu().
+ if (mMenu != null) {
+ MenuItem newTabMenuItem = mMenu.findItem(R.id.new_tab);
+ if (newTabMenuItem != null) {
+ newTabMenuItem.setVisible(mOnlyPrivateTabs == false);
+ }
+ }
+
+ Tabs.getInstance().setOnlyPrivateTabs(mOnlyPrivateTabs);
+ }
+ });
+
Tabs.getInstance().attachToContext(this, mLayerView, getAppEventDispatcher());
Tabs.registerOnTabsChangedListener(this);
@@ -1202,6 +1228,7 @@ public abstract class GeckoApp extends GeckoActivity
}
// If we are doing a restore, read the session data so we can send it to Gecko later.
+
GeckoBundle restoreMessage = null;
if (!mIsRestoringActivity && mShouldRestore) {
final boolean isExternalURL = invokedWithExternalURL(getIntentURI(new SafeIntent(getIntent())));
@@ -2306,6 +2333,10 @@ public abstract class GeckoApp extends GeckoActivity
}
}
+ public boolean isOnlyPrivateTabs() {
+ return mOnlyPrivateTabs;
+ }
+
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
protected void finishAndShutdown(final boolean restart) {
ThreadUtils.assertOnUiThread();
diff --git a/mobile/android/base/java/org/mozilla/gecko/Tabs.java b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
index 068d8cb7ca28..30c75b68aad8 100644
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -106,6 +106,7 @@ public class Tabs implements BundleEventListener {
private ContentObserver mBookmarksContentObserver;
private PersistTabsRunnable mPersistTabsRunnable;
private int mPrivateClearColor;
+ private boolean mOnlyPrivateTabs = true;
// Close all tabs including normal and private tabs.
@RobocopTarget
@@ -211,6 +212,10 @@ public class Tabs implements BundleEventListener {
}
}
+ public synchronized void setOnlyPrivateTabs(boolean onlyPrivateTabs) {
+ mOnlyPrivateTabs = onlyPrivateTabs;
+ }
+
public void detachFromContext() {
mGeckoView = null;
}
@@ -1026,7 +1031,7 @@ public class Tabs implements BundleEventListener {
// delayLoad implies background tab
boolean background = delayLoad || (flags & LOADURL_BACKGROUND) != 0;
- boolean isPrivate = (flags & LOADURL_PRIVATE) != 0 || (intent != null && intent.getBooleanExtra(PRIVATE_TAB_INTENT_EXTRA, false));
+ boolean isPrivate = (flags & LOADURL_PRIVATE) != 0 || (intent != null && intent.getBooleanExtra(PRIVATE_TAB_INTENT_EXTRA, false)) || mOnlyPrivateTabs;
boolean userEntered = (flags & LOADURL_USER_ENTERED) != 0;
boolean desktopMode = (flags & LOADURL_DESKTOP) != 0;
boolean external = (flags & LOADURL_EXTERNAL) != 0;
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java
index 08c107ebb62d..fc27ac03c929 100644
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java
@@ -167,6 +167,10 @@ public class TabsPanel extends LinearLayout
final View tabNormal = mTabWidget.addTab(R.drawable.tabs_normal, R.string.tabs_normal);
mNormalTabsPanel = tabNormal instanceof ThemedImageButton ? ((ThemedImageButton) tabNormal) : null;
+ if (mActivity.isOnlyPrivateTabs()) {
+ tabNormal.setVisibility(View.GONE);
+ }
+
final View tabPrivate = mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private);
mPrivateTabsPanel = tabPrivate instanceof ThemedImageButton ? ((ThemedImageButton) tabPrivate) : null;
if (mPrivateTabsPanel != null) {
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index e139e59a7f40..f8e9399f51c4 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4410,9 +4410,9 @@ Tab.prototype = {
// Must be called after appendChild so the docShell has been created.
this.setActive(false);
- let isPrivate = "isPrivate" in aParams && aParams.isPrivate;
+ let isPrivate = (("isPrivate" in aParams) && aParams.isPrivate) || Services.prefs.getBoolPref("browser.privatebrowsing.autostart");
if (isPrivate) {
- attrs.privateBrowsingId = 1;
+ attrs['privateBrowsingId'] = 1;
}
this.browser.docShell.setOriginAttributes(attrs);
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Bug 30573 - Sanitize old tabs and wait for tor before opening new tabs
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit dd5be6e82f301448b2c4d3290beeb3729e943817
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Wed May 29 14:05:07 2019 +0000
Bug 30573 - Sanitize old tabs and wait for tor before opening new tabs
---
.../base/java/org/mozilla/gecko/BrowserApp.java | 4 +-
.../base/java/org/mozilla/gecko/GeckoApp.java | 55 +++++++++++++++++++++-
2 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
index c2a463ad6ed1..f03548e30ced 100644
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1134,7 +1134,7 @@ public class BrowserApp extends GeckoApp
*
* mTorStatus provides synchronization across threads.
*/
- private boolean checkTorIsStarted() {
+ public boolean checkTorIsStarted() {
// When tor is started, true. Otherwise, false
mTorStatus = false;
BroadcastReceiver br = setupReceiveTorIsStartedAsync();
@@ -2120,6 +2120,7 @@ public class BrowserApp extends GeckoApp
finishAndShutdown(/* restart */ false);
}
}
+ super.handleMessage(event, message, callback);
break;
case "Sanitize:OpenTabs":
@@ -3019,6 +3020,7 @@ public class BrowserApp extends GeckoApp
// If we finished, then Tor bootstrapped 100%
mTorNeedsStart = false;
+ EventDispatcher.getInstance().dispatch("Tor:Ready", null);
// When bootstrapping completes, check if the Firstrun (onboarding) screens
// should be shown.
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
index 9c66f56b0485..388d7c3b89a3 100644
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -193,6 +193,10 @@ public abstract class GeckoApp extends GeckoActivity
protected Menu mMenu;
protected boolean mIsRestoringActivity;
+ protected boolean mIsSanitizeTabsEnabled = false;
+ protected boolean mIsSanitizeCompleted = false;
+ protected Object mIsSanitizeCompletedLock = new Object();
+
/** Tells if we're aborting app launch, e.g. if this is an unsupported device configuration. */
protected boolean mIsAbortingAppLaunch;
@@ -604,6 +608,12 @@ public abstract class GeckoApp extends GeckoActivity
for (final String clear : clearSet) {
clearObj.putBoolean(clear, true);
}
+
+ synchronized (mIsSanitizeCompletedLock) {
+ mIsSanitizeTabsEnabled = clearSet.contains("private.data.openTabs");
+ mIsSanitizeCompleted = false;
+ }
+
return clearObj;
}
@@ -788,6 +798,11 @@ public abstract class GeckoApp extends GeckoActivity
notifyAll();
}
+ } else if ("Sanitize:Finished".equals(event)) {
+ synchronized (mIsSanitizeCompletedLock) {
+ mIsSanitizeCompleted = true;
+ }
+
} else if ("SystemUI:Visibility".equals(event)) {
if (message.getBoolean("visible", true)) {
mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
@@ -1628,7 +1643,45 @@ public abstract class GeckoApp extends GeckoActivity
loadStartupTab(Tabs.LOADURL_NEW_TAB, action);
} else {
final int flags = getNewTabFlags();
- loadStartupTab(passedUri, intent, flags);
+ final BrowserApp browserApp = (BrowserApp) GeckoApp.this;
+
+ synchronized (mIsSanitizeCompletedLock) {
+ // If OpenTabs will be sanitized, then load the tab after Sanitize:Finished
+ // is received. If Tor isn't started, then load tabs after Tor:Ready, too. And
+ // if Gecko isn't loaded, then wait until the profile is loaded (avoiding the race
+ // between loading the page and checking if |browser.privatebrowsing.autoStart| is true.
+ EventDispatcher.getInstance().registerUiThreadListener(new BundleEventListener() {
+ // isSanitized is true if Sanitizing is enable and it is completed or if Sanitizing is disabled.
+ private boolean isSanitized = (mIsSanitizeTabsEnabled && mIsSanitizeCompleted) || !mIsSanitizeTabsEnabled;
+ private boolean isTorReady = browserApp.checkTorIsStarted();
+ private boolean isGeckoReady = GeckoThread.isRunning();
+
+ @Override
+ public void handleMessage(String event, GeckoBundle message, EventCallback callback) {
+ if ("Sanitize:Finished".equals(event)) {
+ EventDispatcher.getInstance().unregisterUiThreadListener(this, "Sanitize:Finished");
+ isSanitized = true;
+
+ } else if ("Tor:Ready".equals(event)) {
+ EventDispatcher.getInstance().unregisterUiThreadListener(this, "Tor:Ready");
+ isTorReady = true;
+ } else if ("Gecko:Ready".equals(event)) {
+ EventDispatcher.getInstance().unregisterUiThreadListener(this, "Gecko:Ready");
+ isGeckoReady = true;
+ } else if ("Tor:CheckIfReady".equals(event)) {
+ EventDispatcher.getInstance().unregisterUiThreadListener(this, "Tor:CheckIfReady");
+ }
+
+ if (isSanitized && isTorReady && isGeckoReady) {
+ loadStartupTab(passedUri, intent, flags);
+ }
+ }
+ }, "Sanitize:Finished", "Tor:Ready", "Tor:CheckIfReady", "Gecko:Ready");
+
+ // Run the event callback now, just in case Tor:Ready and Sanitize:Finished were
+ // dispatched before the listener was created.
+ EventDispatcher.getInstance().dispatch("Tor:CheckIfReady", null);
+ }
}
}
});
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Bug 29859: Disable HLS support for now
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit 14b7000757ad06197cb4e088731171f0a6ff325a
Author: Georg Koppen <gk(a)torproject.org>
Date: Thu Mar 28 19:10:34 2019 +0000
Bug 29859: Disable HLS support for now
---
mobile/android/app/000-tor-browser-android.js | 3 +++
mobile/android/base/AppConstants.java.in | 2 +-
mobile/android/moz.configure | 2 +-
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/mobile/android/app/000-tor-browser-android.js b/mobile/android/app/000-tor-browser-android.js
index 02e3ef00c1b3..2410706bb5f5 100644
--- a/mobile/android/app/000-tor-browser-android.js
+++ b/mobile/android/app/000-tor-browser-android.js
@@ -57,5 +57,8 @@ pref("general.useragent.updates.url", "");
// Enable touch events on Android (highlighting text, etc)
pref("dom.w3c_touch_events.enabled", 2);
+// No HLS support for now due to browser freezing, see: #29859.
+pref("media.hls.enabled", false);
+
// Inherit locale from the OS, used for multi-locale builds
pref("intl.locale.requested", "");
diff --git a/mobile/android/base/AppConstants.java.in b/mobile/android/base/AppConstants.java.in
index 26047188841c..762db8d86a1b 100644
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -382,7 +382,7 @@ public class AppConstants {
//#ifdef TOR_BROWSER_VERSION
return true;
//#else
- return false;
+ return true;
//#endif
}
}
diff --git a/mobile/android/moz.configure b/mobile/android/moz.configure
index c8ed3a9a59e1..cb536b790d94 100644
--- a/mobile/android/moz.configure
+++ b/mobile/android/moz.configure
@@ -92,7 +92,7 @@ project_flag('MOZ_SWITCHBOARD',
project_flag('MOZ_ANDROID_HLS_SUPPORT',
help='Enable HLS (HTTP Live Streaming) support (currently using the ExoPlayer library)',
- default=True)
+ default=False)
option(env='MOZ_ANDROID_ACTIVITY_STREAM',
help='Enable Activity Stream on Android (replacing the default HomePager)',
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Bug 31058: Remove Linus' default bridges
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit 225b1f3bb0a021b52b69b915400266478fb2a264
Author: Georg Koppen <gk(a)torproject.org>
Date: Tue Jul 2 07:57:08 2019 +0000
Bug 31058: Remove Linus' default bridges
There are no default obfs3 bridges remaining after Linus' ones are gone.
Remove the UI and localization.
---
.../res/layout/preference_tor_network_select_bridge_type.xml | 11 -----------
.../java/org/mozilla/gecko/torbootstrap/TorPreferences.java | 3 ---
mobile/android/base/locales/en-US/torbrowser_strings.dtd | 1 -
mobile/android/base/strings.xml.in | 1 -
4 files changed, 16 deletions(-)
diff --git a/mobile/android/app/src/main/res/layout/preference_tor_network_select_bridge_type.xml b/mobile/android/app/src/main/res/layout/preference_tor_network_select_bridge_type.xml
index 2c1632bb8268..4ef5925e233d 100644
--- a/mobile/android/app/src/main/res/layout/preference_tor_network_select_bridge_type.xml
+++ b/mobile/android/app/src/main/res/layout/preference_tor_network_select_bridge_type.xml
@@ -74,17 +74,6 @@
android:textSize="16sp"
android:text="@string/pref_bridges_type_meek_azure"/>
<include layout="@xml/separator" />
- <RadioButton android:id="@+id/radio_pref_bridges_obfs3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10sp"
- android:layout_marginBottom="10sp"
- android:buttonTint="@color/tor_bridges_select_builtin"
- android:fontFamily="Roboto-Regular"
- android:textColor="#DE000000"
- android:textSize="16sp"
- android:text="@string/pref_bridges_type_obfs3"/>
- <include layout="@xml/separator" />
</RadioGroup>
</LinearLayout>
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java
index 9e74c49f3f91..cb37f3867ca4 100644
--- a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java
@@ -679,9 +679,6 @@ public class TorPreferences extends AppCompatPreferenceActivity {
case "meek":
buttonId = R.id.radio_pref_bridges_meek_azure;
break;
- case "obfs3":
- buttonId = R.id.radio_pref_bridges_obfs3;
- break;
}
if (buttonId != -1) {
diff --git a/mobile/android/base/locales/en-US/torbrowser_strings.dtd b/mobile/android/base/locales/en-US/torbrowser_strings.dtd
index f3fba468fc0e..8d000f90d561 100644
--- a/mobile/android/base/locales/en-US/torbrowser_strings.dtd
+++ b/mobile/android/base/locales/en-US/torbrowser_strings.dtd
@@ -54,7 +54,6 @@
<!-- When another PT is recommended, change TorNetworkBridgeSelectPreference::saveCurrentCheckedRadioButton(), too -->
<!ENTITY pref_bridges_type_obfs4 "obfs4 (recommended)">
<!ENTITY pref_bridges_type_meek_azure "meek-azure">
-<!ENTITY pref_bridges_type_obfs3 "obfs3">
<!ENTITY pref_tor_network_bridges_enabled_change_builtin "You\'re using a built-in bridge to connect to Tor. Change">
<!ENTITY pref_tor_network_bridges_enabled_change_custom "You\'re using a custom bridge to connect to Tor. Change">
<!ENTITY pref_tor_network_using_multiple_provided_bridges "You\'re using multiple custom bridges.">
diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in
index 8ac8248ea1dc..9b016bd4cccd 100644
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -101,7 +101,6 @@
<string name="pref_choice_tor_bridges_enabled_summary">&pref_choice_tor_bridges_enabled_summary;</string>
<string name="pref_bridges_type_obfs4">&pref_bridges_type_obfs4;</string>
<string name="pref_bridges_type_meek_azure">&pref_bridges_type_meek_azure;</string>
- <string name="pref_bridges_type_obfs3">&pref_bridges_type_obfs3;</string>
<string name="pref_tor_bridges_provide_manual_button_title">&pref_tor_bridges_provide_manual_button_title;</string>
<string name="pref_tor_bridges_provide_select_text_title">&pref_tor_bridges_provide_select_text_title;</string>
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Bug 26690: Port padlock states for .onion services to mobile
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit daa148cebd3da69fa24739e2910d67b7480b4fdd
Author: Igor Oliveira <igt0(a)torproject.org>
Date: Mon Oct 29 12:30:14 2018 -0200
Bug 26690: Port padlock states for .onion services to mobile
Prior to this patch, TBA was showing onion services as insecure
connection and SSL/TLS ones as encrypted connections(lock icon).
This patch fixes the issue adding several new onion icons to indicate
all the various permutations of onions services hosted HTTP or HTTPS
pages.
---
.../app/src/main/res/drawable-hdpi/ic_onion.png | Bin 0 -> 807 bytes
.../main/res/drawable-hdpi/ic_onion_disabled.png | Bin 0 -> 975 bytes
.../src/main/res/drawable-hdpi/ic_onion_lock.png | Bin 0 -> 932 bytes
.../app/src/main/res/drawable-xhdpi/ic_onion.png | Bin 0 -> 1015 bytes
.../main/res/drawable-xhdpi/ic_onion_disabled.png | Bin 0 -> 1260 bytes
.../src/main/res/drawable-xhdpi/ic_onion_lock.png | Bin 0 -> 1275 bytes
.../app/src/main/res/drawable-xxhdpi/ic_onion.png | Bin 0 -> 1592 bytes
.../main/res/drawable-xxhdpi/ic_onion_disabled.png | Bin 0 -> 1892 bytes
.../src/main/res/drawable-xxhdpi/ic_onion_lock.png | Bin 0 -> 1899 bytes
.../app/src/main/res/drawable-xxxhdpi/ic_onion.png | Bin 0 -> 2099 bytes
.../res/drawable-xxxhdpi/ic_onion_disabled.png | Bin 0 -> 2526 bytes
.../main/res/drawable-xxxhdpi/ic_onion_lock.png | Bin 0 -> 2568 bytes
.../main/res/drawable/security_mode_icon_nm.xml | 9 +++++++++
.../main/res/drawable/security_mode_icon_pm.xml | 9 +++++++++
.../src/main/res/drawable/site_security_icon.xml | 10 +++++++++-
.../base/java/org/mozilla/gecko/SiteIdentity.java | 14 ++++++++++++++
.../mozilla/gecko/toolbar/SecurityModeUtil.java | 21 ++++++++++++++++-----
.../mozilla/gecko/toolbar/SiteIdentityPopup.java | 17 +++++++++++------
mobile/android/chrome/content/browser.js | 15 ++++++++++++++-
19 files changed, 82 insertions(+), 13 deletions(-)
diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_onion.png b/mobile/android/app/src/main/res/drawable-hdpi/ic_onion.png
new file mode 100644
index 000000000000..1a61d982752b
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-hdpi/ic_onion.png differ
diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_onion_disabled.png b/mobile/android/app/src/main/res/drawable-hdpi/ic_onion_disabled.png
new file mode 100644
index 000000000000..9669d12101fb
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-hdpi/ic_onion_disabled.png differ
diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_onion_lock.png b/mobile/android/app/src/main/res/drawable-hdpi/ic_onion_lock.png
new file mode 100644
index 000000000000..b0f60fea5b28
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-hdpi/ic_onion_lock.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion.png b/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion.png
new file mode 100755
index 000000000000..074330c3a25a
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion_disabled.png b/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion_disabled.png
new file mode 100755
index 000000000000..09db37998d33
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion_disabled.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion_lock.png b/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion_lock.png
new file mode 100755
index 000000000000..13799b7fa8f7
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xhdpi/ic_onion_lock.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion.png b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion.png
new file mode 100755
index 000000000000..711bb3c8d8fa
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion_disabled.png b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion_disabled.png
new file mode 100755
index 000000000000..0d931669abf3
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion_disabled.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion_lock.png b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion_lock.png
new file mode 100755
index 000000000000..f7a5c29b4c17
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_onion_lock.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion.png b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion.png
new file mode 100755
index 000000000000..97a0beabbe0c
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion_disabled.png b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion_disabled.png
new file mode 100755
index 000000000000..aec29bf6238b
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion_disabled.png differ
diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion_lock.png b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion_lock.png
new file mode 100755
index 000000000000..71df527b2a6a
Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_onion_lock.png differ
diff --git a/mobile/android/app/src/main/res/drawable/security_mode_icon_nm.xml b/mobile/android/app/src/main/res/drawable/security_mode_icon_nm.xml
index 0b6b496b0b34..bb0da0b843d2 100644
--- a/mobile/android/app/src/main/res/drawable/security_mode_icon_nm.xml
+++ b/mobile/android/app/src/main/res/drawable/security_mode_icon_nm.xml
@@ -28,5 +28,14 @@
<item
android:drawable="@drawable/ic_search_icon"
android:maxLevel="6" />
+ <item
+ android:drawable="@drawable/ic_onion"
+ android:maxLevel="7"/>
+ <item
+ android:drawable="@drawable/ic_onion_lock"
+ android:maxLevel="8"/>
+ <item
+ android:drawable="@drawable/ic_onion_disabled"
+ android:maxLevel="9"/>
</level-list>
diff --git a/mobile/android/app/src/main/res/drawable/security_mode_icon_pm.xml b/mobile/android/app/src/main/res/drawable/security_mode_icon_pm.xml
index edbd269040d1..be47d7fe081f 100644
--- a/mobile/android/app/src/main/res/drawable/security_mode_icon_pm.xml
+++ b/mobile/android/app/src/main/res/drawable/security_mode_icon_pm.xml
@@ -28,5 +28,14 @@
<item
android:drawable="@drawable/ic_search_icon"
android:maxLevel="6" />
+ <item
+ android:drawable="@drawable/ic_onion"
+ android:maxLevel="7"/>
+ <item
+ android:drawable="@drawable/ic_onion_lock"
+ android:maxLevel="8"/>
+ <item
+ android:drawable="@drawable/ic_onion_disabled"
+ android:maxLevel="9"/>
</level-list>
diff --git a/mobile/android/app/src/main/res/drawable/site_security_icon.xml b/mobile/android/app/src/main/res/drawable/site_security_icon.xml
index ac8624f861ad..f5f4c7775110 100644
--- a/mobile/android/app/src/main/res/drawable/site_security_icon.xml
+++ b/mobile/android/app/src/main/res/drawable/site_security_icon.xml
@@ -28,5 +28,13 @@
<item
android:drawable="@drawable/ic_search_icon"
android:maxLevel="6" />
-
+ <item
+ android:drawable="@drawable/ic_onion"
+ android:maxLevel="7"/>
+ <item
+ android:drawable="@drawable/ic_onion_lock"
+ android:maxLevel="8"/>
+ <item
+ android:drawable="@drawable/ic_onion_disabled"
+ android:maxLevel="9"/>
</level-list>
diff --git a/mobile/android/base/java/org/mozilla/gecko/SiteIdentity.java b/mobile/android/base/java/org/mozilla/gecko/SiteIdentity.java
index 79262586b8fe..0a1b92b75f96 100644
--- a/mobile/android/base/java/org/mozilla/gecko/SiteIdentity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SiteIdentity.java
@@ -25,6 +25,8 @@ public class SiteIdentity {
private String mCountry;
private String mVerifier;
private String mOrigin;
+ private boolean mIsOnionHost;
+ private boolean mHasCert;
public enum SecurityMode {
UNKNOWN,
@@ -59,6 +61,8 @@ public class SiteIdentity {
mCountry = null;
mVerifier = null;
mSecure = false;
+ mIsOnionHost = false;
+ mHasCert = false;
}
public void reset() {
@@ -108,6 +112,8 @@ public class SiteIdentity {
mVerifier = identityData.getString("verifier");
mSecure = identityData.getBoolean("secure");
mSecurityException = identityData.getBoolean("securityException");
+ mIsOnionHost = identityData.getBoolean("isOnionHost");
+ mHasCert = identityData.getBoolean("hasCert");
}
/* package */ void updateTrackingMode(final String trackingEvent) {
@@ -154,6 +160,14 @@ public class SiteIdentity {
return mSecure;
}
+ public boolean isOnionHost() {
+ return mIsOnionHost;
+ }
+
+ public boolean hasCert() {
+ return mHasCert;
+ }
+
public MixedMode getMixedModeActive() {
return mMixedModeActive;
}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/SecurityModeUtil.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/SecurityModeUtil.java
index ceb33b8e3acb..10bc83c279a1 100644
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/SecurityModeUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/SecurityModeUtil.java
@@ -35,6 +35,9 @@ public class SecurityModeUtil {
LOCK_SECURE(1),
LOCK_WARNING(-1), // not used for now. reserve for MixedDisplayContent icon, if any.
LOCK_INSECURE(3),
+ ONION(7),
+ ONION_ACTIVATE(8),
+ ONION_DISABLED(9),
WARNING(2),
TRACKING_CONTENT_BLOCKED(4),
TRACKING_CONTENT_LOADED(5);
@@ -100,6 +103,8 @@ public class SecurityModeUtil {
final MixedMode displayMixedMode = identity.getMixedModeDisplay();
final TrackingMode trackingMode = identity.getTrackingMode();
final boolean securityException = identity.isSecurityException();
+ final boolean isOnionHost = identity.isOnionHost();
+ final boolean hasCert = identity.hasCert();
if (securityException) {
return IconType.WARNING;
@@ -108,9 +113,9 @@ public class SecurityModeUtil {
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
return IconType.TRACKING_CONTENT_BLOCKED;
} else if (activeMixedMode == MixedMode.LOADED) {
- return IconType.LOCK_INSECURE;
+ return isOnionHost ? IconType.ONION_DISABLED : IconType.LOCK_INSECURE;
} else if (displayMixedMode == MixedMode.LOADED) {
- return IconType.WARNING;
+ return isOnionHost ? IconType.ONION_DISABLED : IconType.WARNING;
}
// Chrome-UI checking is after tracking/mixed-content, even for about: pages, as they
@@ -119,9 +124,15 @@ public class SecurityModeUtil {
return IconType.DEFAULT;
}
- return securityModeMap.containsKey(securityMode)
- ? securityModeMap.get(securityMode)
- : IconType.UNKNOWN;
+ if (securityMode == SecurityMode.UNKNOWN) {
+ return isOnionHost ? IconType.ONION : IconType.UNKNOWN;
+ } else if (securityMode == SecurityMode.IDENTIFIED) {
+ return isOnionHost ? (hasCert ? IconType.ONION_ACTIVATE : IconType.ONION) : IconType.LOCK_SECURE;
+ } else if (securityMode == SecurityMode.VERIFIED) {
+ return isOnionHost ? IconType.ONION_ACTIVATE : IconType.LOCK_SECURE;
+ } else {
+ return IconType.UNKNOWN;
+ }
}
/**
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
index 831f69f2bf09..df67f0a9b9ed 100644
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
@@ -144,8 +144,9 @@ public class SiteIdentityPopup extends AnchoredPopup implements BundleEventListe
init();
}
- final boolean isIdentityKnown = (siteIdentity.getSecurityMode() == SecurityMode.IDENTIFIED ||
- siteIdentity.getSecurityMode() == SecurityMode.VERIFIED);
+ final boolean isIdentityKnown = ((siteIdentity.getSecurityMode() == SecurityMode.IDENTIFIED ||
+ siteIdentity.getSecurityMode() == SecurityMode.VERIFIED) &&
+ siteIdentity.hasCert());
updateConnectionState(siteIdentity);
toggleIdentityKnownContainerVisibility(isIdentityKnown);
@@ -322,7 +323,8 @@ public class SiteIdentityPopup extends AnchoredPopup implements BundleEventListe
} else if (!siteIdentity.isSecure()) {
if (siteIdentity.getMixedModeActive() == MixedMode.LOADED) {
// Active Mixed Content loaded because user has disabled blocking.
- mIcon.setImageResource(R.drawable.ic_lock_disabled);
+ int resId = siteIdentity.isOnionHost() ? R.drawable.ic_onion_disabled : R.drawable.ic_lock_disabled;
+ mIcon.setImageResource(resId);
clearSecurityStateIcon();
mMixedContentActivity.setVisibility(View.VISIBLE);
mMixedContentActivity.setText(R.string.mixed_content_protection_disabled);
@@ -330,7 +332,8 @@ public class SiteIdentityPopup extends AnchoredPopup implements BundleEventListe
mLink.setVisibility(View.VISIBLE);
} else if (siteIdentity.getMixedModeDisplay() == MixedMode.LOADED) {
// Passive Mixed Content loaded.
- mIcon.setImageResource(R.drawable.ic_lock_inactive);
+ int resId = siteIdentity.isOnionHost() ? R.drawable.ic_onion_disabled : R.drawable.ic_lock_inactive;
+ mIcon.setImageResource(resId);
setSecurityStateIcon(R.drawable.ic_warning_major, 1);
mMixedContentActivity.setVisibility(View.VISIBLE);
if (siteIdentity.getMixedModeActive() == MixedMode.BLOCKED) {
@@ -342,7 +345,8 @@ public class SiteIdentityPopup extends AnchoredPopup implements BundleEventListe
} else {
// Unencrypted connection with no mixed content.
- mIcon.setImageResource(R.drawable.globe_light);
+ int resId = siteIdentity.isOnionHost() ? R.drawable.ic_onion : R.drawable.globe_light;
+ mIcon.setImageResource(resId);
clearSecurityStateIcon();
mMixedContentActivity.setVisibility(View.GONE);
@@ -361,7 +365,8 @@ public class SiteIdentityPopup extends AnchoredPopup implements BundleEventListe
} else {
// Connection is secure.
- mIcon.setImageResource(R.drawable.ic_lock);
+ int resId = siteIdentity.isOnionHost() ? (siteIdentity.hasCert() ? R.drawable.ic_onion_lock : R.drawable.ic_onion) : R.drawable.ic_lock;
+ mIcon.setImageResource(resId);
setSecurityStateIcon(R.drawable.img_check, 2);
mSecurityState.setTextColor(ContextCompat.getColor(mContext, R.color.affirmative_green));
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index c9cf0f128176..e139e59a7f40 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -6788,6 +6788,17 @@ var IdentityHandler = {
return result;
},
+ isOnionHost: function isOnionHost() {
+ try {
+ return this._uri.host.toLowerCase().endsWith(".onion");
+ } catch (e) {
+ // If something goes wrong (e.g. host is an exception
+ // because this is an about: page) just fall back
+ // on false.
+ return false;
+ }
+ },
+
/**
* Determines the identity mode corresponding to the icon we show in the urlbar.
*/
@@ -6913,6 +6924,8 @@ var IdentityHandler = {
};
result.host = this.getEffectiveHost();
+ result.isOnionHost = this.isOnionHost();
+ result.hasCert = !!this._lastStatus;
// Don't show identity data for pages with an unknown identity or if any
// mixed content is loaded (mixed display content is loaded by default).
@@ -6982,7 +6995,7 @@ var IdentityHandler = {
// Updating the tooltip value in those cases isn't critical.
// FIXME: Fixing bug 646690 would probably makes this check unnecessary
if (
- this._lastLocation.hostname &&
+ this._lastLocation.hostname && iData.cert &&
this._overrideService.hasMatchingOverride(
this._lastLocation.hostname,
this._lastLocation.port || 443,
1
0

[tor-browser/tor-browser-68.1.0esr-9.0-1] Bug 28329 - Part 4. Add new Tor Bootstrapping and configuration screens
by gk@torproject.org 31 Aug '19
by gk@torproject.org 31 Aug '19
31 Aug '19
commit 385689ddc6c13420a40f1f813051ec0af99f306d
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Thu Mar 14 02:03:26 2019 +0000
Bug 28329 - Part 4. Add new Tor Bootstrapping and configuration screens
Also:
Bug 30214 - Kill background thread when Activity is null
Bug 30239 - Render Fragments after crash
Bug 29982 - Force single-pane UI on Tor Preferences
---
.../android/app/src/main/res/layout/gecko_app.xml | 5 +
.../preference_tor_network_bridge_summary.xml | 25 +
.../preference_tor_network_bridges_enabled.xml | 85 ++
...eference_tor_network_bridges_enabled_switch.xml | 15 +
.../preference_tor_network_provide_bridge.xml | 89 ++
.../preference_tor_network_select_bridge_type.xml | 128 +++
.../app/src/main/res/layout/tor_bootstrap.xml | 83 ++
.../layout/tor_bootstrap_animation_container.xml | 20 +
.../app/src/main/res/layout/tor_bootstrap_log.xml | 37 +
.../main/res/xml/preferences_tor_network_main.xml | 15 +
.../xml/preferences_tor_network_provide_bridge.xml | 27 +
.../preferences_tor_network_select_bridge_type.xml | 17 +
mobile/android/base/AndroidManifest.xml.in | 5 +
.../base/java/org/mozilla/gecko/BrowserApp.java | 52 +-
.../TorBootstrapAnimationContainer.java | 82 ++
.../gecko/torbootstrap/TorBootstrapLogPanel.java | 54 ++
.../gecko/torbootstrap/TorBootstrapLogger.java | 17 +
.../gecko/torbootstrap/TorBootstrapPager.java | 203 +++++
.../torbootstrap/TorBootstrapPagerConfig.java | 48 +
.../gecko/torbootstrap/TorBootstrapPanel.java | 575 ++++++++++++
.../gecko/torbootstrap/TorLogEventListener.java | 128 +++
.../mozilla/gecko/torbootstrap/TorPreferences.java | 975 +++++++++++++++++++++
22 files changed, 2680 insertions(+), 5 deletions(-)
diff --git a/mobile/android/app/src/main/res/layout/gecko_app.xml b/mobile/android/app/src/main/res/layout/gecko_app.xml
index f48e7fc9f3be..d6a6133496e2 100644
--- a/mobile/android/app/src/main/res/layout/gecko_app.xml
+++ b/mobile/android/app/src/main/res/layout/gecko_app.xml
@@ -63,6 +63,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
+ <ViewStub android:id="@+id/tor_bootstrap_pager_stub"
+ android:layout="@layout/tor_bootstrap_animation_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
</FrameLayout>
<View android:id="@+id/doorhanger_overlay"
diff --git a/mobile/android/app/src/main/res/layout/preference_tor_network_bridge_summary.xml b/mobile/android/app/src/main/res/layout/preference_tor_network_bridge_summary.xml
new file mode 100644
index 000000000000..d99b3c9543b0
--- /dev/null
+++ b/mobile/android/app/src/main/res/layout/preference_tor_network_bridge_summary.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical" >
+ <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tor_network_bridge_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="30sp"
+ android:paddingBottom="30sp"
+ android:paddingLeft="20sp"
+ android:paddingRight="20sp"
+ android:textSize="16sp"
+ android:fontFamily="Roboto-Regular"
+ android:textColor="#DE000000"
+ android:lineSpacingMultiplier="1.43"
+ android:text="@string/pref_category_tor_bridge_summary" />
+</LinearLayout>
diff --git a/mobile/android/app/src/main/res/layout/preference_tor_network_bridges_enabled.xml b/mobile/android/app/src/main/res/layout/preference_tor_network_bridges_enabled.xml
new file mode 100644
index 000000000000..8d8e4f320ba7
--- /dev/null
+++ b/mobile/android/app/src/main/res/layout/preference_tor_network_bridges_enabled.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 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.
+-->
+<!-- Layout for a Preference in a PreferenceActivity. The
+ Preference is able to place a specific widget for its particular
+ type in the "widget_frame" layout.
+ This is a modified version of the default Android Preference layout,
+ See: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/pie-…
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:paddingEnd="?android:attr/scrollbarSize"
+ android:orientation="vertical"
+ android:background="?android:attr/selectableItemBackground" >
+ <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tor_network_configuration_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="30sp"
+ android:paddingBottom="30sp"
+ android:paddingLeft="20sp"
+ android:paddingRight="20sp"
+ android:textSize="16sp"
+ android:fontFamily="Roboto-Regular"
+ android:textColor="#DE000000"
+ android:lineSpacingMultiplier="1.43"
+ android:text="@string/pref_category_tor_network_summary" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical" >
+ <ImageView
+ android:id="@+android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ />
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="15dp"
+ android:layout_marginEnd="6dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginBottom="12dp"
+ android:layout_weight="1">
+ <TextView android:id="@+android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:fontFamily="Roboto-Regular"
+ android:textSize="20sp"
+ android:textColor="#DE000000"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+ <TextView android:id="@+android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:fontFamily="Roboto-Regular"
+ android:textSize="16sp"
+ android:textColorLink="#8000FF"
+ android:clickable="true"
+ android:focusable="false"
+ android:maxLines="2" />
+ </RelativeLayout>
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout android:id="@+android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/mobile/android/app/src/main/res/layout/preference_tor_network_bridges_enabled_switch.xml b/mobile/android/app/src/main/res/layout/preference_tor_network_bridges_enabled_switch.xml
new file mode 100644
index 000000000000..3ab276f0916c
--- /dev/null
+++ b/mobile/android/app/src/main/res/layout/preference_tor_network_bridges_enabled_switch.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<Switch xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+android:id/switch_widget"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:clickable="true"
+ android:thumbTint="@color/tor_bridges_enabled_colors"
+ android:trackTint="@color/tor_bridges_enabled_colors"
+ android:background="@null" />
diff --git a/mobile/android/app/src/main/res/layout/preference_tor_network_provide_bridge.xml b/mobile/android/app/src/main/res/layout/preference_tor_network_provide_bridge.xml
new file mode 100644
index 000000000000..9e72b44ae734
--- /dev/null
+++ b/mobile/android/app/src/main/res/layout/preference_tor_network_provide_bridge.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="vertical"
+ android:paddingEnd="?android:attr/scrollbarSize"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground" >
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16sp"
+ android:paddingRight="16sp"
+ android:orientation="vertical" >
+ <TextView android:id="@+android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="Roboto-Regular"
+ android:textSize="20sp"
+ android:textColor="#DE000000"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+ <TextView android:id="@+android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="30sp"
+ android:fontFamily="Roboto-Regular"
+ android:textSize="16sp"
+ android:maxLines="4" />
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:orientation="vertical" >
+ <EditText
+ android:id="@+id/tor_network_provide_bridge1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:inputType="text"
+ android:textSize="20sp"
+ android:fontFamily="Roboto-Regular"
+ android:paddingTop="22sp"
+ android:paddingLeft="16sp"
+ android:paddingBottom="22sp"
+ android:background="#DCDCDC"
+ android:hint="@string/pref_tor_bridges_provide_manual_address_port_placeholder" />
+ <EditText
+ android:id="@+id/tor_network_provide_bridge2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:inputType="text"
+ android:textSize="20sp"
+ android:fontFamily="Roboto-Regular"
+ android:paddingTop="22sp"
+ android:paddingLeft="16sp"
+ android:paddingBottom="22sp"
+ android:background="#DCDCDC"
+ android:hint="@string/pref_tor_bridges_provide_manual_address_port_placeholder" />
+ <EditText
+ android:id="@+id/tor_network_provide_bridge3"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="text"
+ android:textSize="20sp"
+ android:fontFamily="Roboto-Regular"
+ android:paddingTop="22sp"
+ android:paddingLeft="16sp"
+ android:paddingBottom="22sp"
+ android:background="#DCDCDC"
+ android:hint="@string/pref_tor_bridges_provide_manual_address_port_placeholder" />
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout android:id="@+android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+ </LinearLayout>
+</LinearLayout>
diff --git a/mobile/android/app/src/main/res/layout/preference_tor_network_select_bridge_type.xml b/mobile/android/app/src/main/res/layout/preference_tor_network_select_bridge_type.xml
new file mode 100644
index 000000000000..2c1632bb8268
--- /dev/null
+++ b/mobile/android/app/src/main/res/layout/preference_tor_network_select_bridge_type.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="vertical"
+ android:paddingEnd="?android:attr/scrollbarSize"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <!-- Include the Bridge description -->
+ <include layout="@layout/preference_tor_network_bridge_summary" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20sp"
+ android:orientation="vertical" >
+ <LinearLayout
+ android:id="@+id/title_and_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ <TextView android:id="@+android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="Roboto-Regular"
+ android:textSize="20sp"
+ android:textColor="#DE000000"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+ <TextView android:id="@+android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="40dp"
+ android:fontFamily="Roboto-Regular"
+ android:textSize="16sp"
+ android:maxLines="4" />
+ </LinearLayout>
+ <include layout="@xml/separator" />
+ <RadioGroup
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="20sp"
+ android:visibility="gone"
+ android:layoutDirection="rtl"
+ android:id="@+id/pref_radio_group_builtin_bridges_type">
+ <RadioButton android:id="@+id/radio_pref_bridges_obfs4"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10sp"
+ android:layout_marginBottom="10sp"
+ android:buttonTint="@color/tor_bridges_select_builtin"
+ android:fontFamily="Roboto-Regular"
+ android:textColor="#DE000000"
+ android:textSize="16sp"
+ android:text="@string/pref_bridges_type_obfs4"/>
+ <include layout="@xml/separator" />
+ <RadioButton android:id="@+id/radio_pref_bridges_meek_azure"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10sp"
+ android:layout_marginBottom="10sp"
+ android:buttonTint="@color/tor_bridges_select_builtin"
+ android:fontFamily="Roboto-Regular"
+ android:textColor="#DE000000"
+ android:textSize="16sp"
+ android:text="@string/pref_bridges_type_meek_azure"/>
+ <include layout="@xml/separator" />
+ <RadioButton android:id="@+id/radio_pref_bridges_obfs3"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10sp"
+ android:layout_marginBottom="10sp"
+ android:buttonTint="@color/tor_bridges_select_builtin"
+ android:fontFamily="Roboto-Regular"
+ android:textColor="#DE000000"
+ android:textSize="16sp"
+ android:text="@string/pref_bridges_type_obfs3"/>
+ <include layout="@xml/separator" />
+ </RadioGroup>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:paddingTop="20sp"
+ android:paddingLeft="20sp"
+ android:id="@+id/tor_network_provide_a_bridge"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/tor_network_provide_a_bridge_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="Roboto-Regular"
+ android:textSize="20sp"
+ android:textColor="#DE000000"
+ android:text="@string/pref_tor_bridges_provide_manual_button_title"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+ <TextView
+ android:id="@+id/tor_network_provide_a_bridge_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="30dp"
+ android:fontFamily="Roboto-Regular"
+ android:textSize="16sp"
+ android:text="@string/pref_tor_bridges_provide_manual_summary"
+ android:maxLines="4" />
+ <include layout="@xml/separator" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/mobile/android/app/src/main/res/layout/tor_bootstrap.xml b/mobile/android/app/src/main/res/layout/tor_bootstrap.xml
new file mode 100644
index 000000000000..af9c7d11d3f2
--- /dev/null
+++ b/mobile/android/app/src/main/res/layout/tor_bootstrap.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tor_bootstrap_background">
+
+ <ImageView android:id="@+id/tor_bootstrap_settings_gear"
+ app:srcCompat="@drawable/ic_settings_24px"
+ android:tint="#ffffffff"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:layout_marginRight="20dp"
+ android:layout_alignParentRight="true" />
+
+ <!-- These three elements are rendered in reverse order -->
+ <TextView android:id="@+id/tor_bootstrap_swipe_log"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:width="301dp"
+ android:height="24dp"
+ android:layout_marginBottom="20dp"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:gravity="center"
+ android:visibility="invisible"
+ android:textSize="14sp"
+ android:fontFamily="Roboto-Regular"
+ android:textColor="#FFFFFFFF"
+ android:lineSpacingMultiplier="1.71"
+ android:text="@string/tor_bootstrap_swipe_for_logs"/>
+
+ <Button android:id="@+id/tor_bootstrap_connect"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="7dp"
+ android:width="144dp"
+ android:height="48dp"
+ android:textSize="14sp"
+ android:layout_above="@id/tor_bootstrap_swipe_log"
+ android:layout_centerHorizontal="true"
+ android:background="@drawable/rounded_corners"
+ android:fontFamily="Roboto-Medium"
+ android:textColor="@color/tor_bootstrap_background"
+ android:lineSpacingMultiplier="1.14"
+ android:text="@string/tor_bootstrap_connect" />
+
+ <TextView android:id="@+id/tor_bootstrap_last_status_message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:width="301dp"
+ android:height="24dp"
+ android:layout_marginBottom="40dp"
+ android:layout_above="@id/tor_bootstrap_connect"
+ android:layout_centerHorizontal="true"
+ android:gravity="center"
+ android:singleLine="true"
+ android:textSize="14sp"
+ android:fontFamily="RobotoMono-Regular"
+ android:textColor="@android:color/white"
+ android:lineSpacingMultiplier="2"
+ android:visibility="invisible" />
+
+ <!-- Keep the src synchronized with TorBootstrapPanel::stopBootstrapping() -->
+ <ImageView android:id="@+id/tor_bootstrap_onion"
+ app:srcCompat="@drawable/tor_spinning_onion"
+ android:scaleType="fitCenter"
+ android:tint="#ffffffff"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_marginBottom="37dp"
+ android:layout_marginRight="10dp"
+ android:layout_marginLeft="10dp"
+ android:layout_centerHorizontal="true"
+ android:layout_below="@id/tor_bootstrap_settings_gear"
+ android:layout_above="@id/tor_bootstrap_last_status_message" />
+</RelativeLayout>
diff --git a/mobile/android/app/src/main/res/layout/tor_bootstrap_animation_container.xml b/mobile/android/app/src/main/res/layout/tor_bootstrap_animation_container.xml
new file mode 100644
index 000000000000..04dfeb0f3509
--- /dev/null
+++ b/mobile/android/app/src/main/res/layout/tor_bootstrap_animation_container.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<org.mozilla.gecko.torbootstrap.TorBootstrapAnimationContainer xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:background="@color/tor_bootstrap_background">
+
+ <org.mozilla.gecko.torbootstrap.TorBootstrapPager
+ android:id="@+id/tor_bootstrap_pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tor_bootstrap_background">
+
+ </org.mozilla.gecko.torbootstrap.TorBootstrapPager>
+</org.mozilla.gecko.torbootstrap.TorBootstrapAnimationContainer>
diff --git a/mobile/android/app/src/main/res/layout/tor_bootstrap_log.xml b/mobile/android/app/src/main/res/layout/tor_bootstrap_log.xml
new file mode 100644
index 000000000000..c2f02d658d50
--- /dev/null
+++ b/mobile/android/app/src/main/res/layout/tor_bootstrap_log.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tor_bootstrap_background">
+
+
+ <ImageView android:id="@+id/tor_bootstrap_settings_gear"
+ app:srcCompat="@drawable/ic_settings_24px"
+ android:tint="#ffffffff"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:layout_marginRight="20dp"
+ android:layout_alignParentRight="true" />
+
+ <!-- Encapsulate the TextView within the ScrollView so the view is scrollable -->
+ <ScrollView android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:layout_below="@id/tor_bootstrap_settings_gear" >
+ <TextView android:id="@+id/tor_bootstrap_last_status_message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/white"
+ android:fontFamily="RobotoMono-Regular"
+ android:textSize="14sp"
+ android:textIsSelectable="true"
+ android:layout_marginLeft="20dp"
+ android:layout_marginRight="20dp" />
+ </ScrollView>
+</RelativeLayout>
diff --git a/mobile/android/app/src/main/res/xml/preferences_tor_network_main.xml b/mobile/android/app/src/main/res/xml/preferences_tor_network_main.xml
new file mode 100644
index 000000000000..c397bd7c1fc9
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/preferences_tor_network_main.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto"
+ android:enabled="true">
+ <SwitchPreference android:key="android.not_a_preference.tor.bridges.enabled"
+ android:title="@string/pref_choice_tor_bridges_enabled_title"
+ android:summaryOff="@string/pref_choice_tor_bridges_enabled_summary"
+ android:selectable="false"
+ android:layout="@layout/preference_tor_network_bridges_enabled"
+ android:widgetLayout="@layout/preference_tor_network_bridges_enabled_switch" />
+</PreferenceScreen>
diff --git a/mobile/android/app/src/main/res/xml/preferences_tor_network_provide_bridge.xml b/mobile/android/app/src/main/res/xml/preferences_tor_network_provide_bridge.xml
new file mode 100644
index 000000000000..e8346f4fec63
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/preferences_tor_network_provide_bridge.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto"
+ android:enabled="true">
+
+
+ <!-- Ideally, this preference would not be needed. We would move the
+ summary into the tor.bridges.provide preference. However, there is
+ a bug in the layout where typing in the text field isn't shown until
+ the user presses the back button. This only occurs when the EditText
+ View is under the first ViewGroup under the ListView. -->
+ <Preference
+ android:layout="@layout/preference_tor_network_bridge_summary"
+ android:selectable="false"
+ android:shouldDisableView="false"
+ android:enabled="false"/>
+
+ <Preference
+ android:key="android.not_a_preference.tor.bridges.provide"
+ android:layout="@layout/preference_tor_network_provide_bridge"
+ android:title="@string/pref_tor_bridges_provide_manual_text_title"
+ android:summary="@string/pref_tor_bridges_provide_manual_summary" />
+</PreferenceScreen>
diff --git a/mobile/android/app/src/main/res/xml/preferences_tor_network_select_bridge_type.xml b/mobile/android/app/src/main/res/xml/preferences_tor_network_select_bridge_type.xml
new file mode 100644
index 000000000000..0bcc18c38997
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/preferences_tor_network_select_bridge_type.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto"
+ android:enabled="true">
+
+ <Preference
+ android:key="android.not_a_preference.tor.bridges.type"
+ android:layout="@layout/preference_tor_network_select_bridge_type"
+ android:title="@string/pref_tor_bridges_provide_select_text_title"
+ android:summary="@string/pref_choice_tor_bridges_enabled_summary"
+ android:selectable="false"/>
+
+</PreferenceScreen>
diff --git a/mobile/android/base/AndroidManifest.xml.in b/mobile/android/base/AndroidManifest.xml.in
index c60210e0332c..228a7b6399b0 100644
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -580,5 +580,10 @@
android:stopWithTask="true">
</service>
+ <activity android:name="org.mozilla.gecko.torbootstrap.TorPreferences"
+ android:theme="@style/Gecko.Preferences"
+ android:configChanges="orientation|screenSize|locale|layoutDirection"
+ android:excludeFromRecents="true"/>
+
</application>
</manifest>
diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
index e0ef8e9c43d9..da25e3b395be 100644
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -153,6 +153,7 @@ import org.mozilla.gecko.toolbar.BrowserToolbar;
import org.mozilla.gecko.toolbar.BrowserToolbar.CommitEventSource;
import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
import org.mozilla.gecko.toolbar.PwaConfirm;
+import org.mozilla.gecko.torbootstrap.TorBootstrapAnimationContainer;
import org.mozilla.gecko.updater.PostUpdateHandler;
import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.ActivityUtils;
@@ -255,6 +256,7 @@ public class BrowserApp extends GeckoApp
// We can't name the TabStrip class because it's not included on API 9.
private TabStripInterface mTabStrip;
private AnimatedProgressBar mProgressView;
+ private TorBootstrapAnimationContainer mTorBootstrapAnimationContainer;
private HomeScreen mHomeScreen;
private TabsPanel mTabsPanel;
@@ -390,7 +392,7 @@ public class BrowserApp extends GeckoApp
Log.d(LOGTAG, "BrowserApp.onTabChanged: " + tab.getId() + ": " + msg);
switch (msg) {
case SELECTED:
- if (Tabs.getInstance().isSelectedTab(tab) && mDynamicToolbar.isEnabled()) {
+ if (Tabs.getInstance().isSelectedTab(tab) && mDynamicToolbar.isEnabled() && !isTorBootstrapVisible()) {
final VisibilityTransition transition = (tab.getShouldShowToolbarWithoutAnimationOnFirstSelection()) ?
VisibilityTransition.IMMEDIATE : VisibilityTransition.ANIMATE;
mDynamicToolbar.setVisible(true, transition);
@@ -400,7 +402,7 @@ public class BrowserApp extends GeckoApp
}
// fall through
case LOCATION_CHANGE:
- if (Tabs.getInstance().isSelectedTab(tab)) {
+ if (Tabs.getInstance().isSelectedTab(tab) && !isTorBootstrapVisible()) {
updateHomePagerForTab(tab);
}
@@ -413,7 +415,7 @@ public class BrowserApp extends GeckoApp
if (Tabs.getInstance().isSelectedTab(tab)) {
invalidateOptionsMenu();
- if (mDynamicToolbar.isEnabled()) {
+ if (mDynamicToolbar.isEnabled() && !isTorBootstrapVisible()) {
mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
}
}
@@ -1191,8 +1193,12 @@ public class BrowserApp extends GeckoApp
final SafeIntent intent = new SafeIntent(getIntent());
if (!IntentUtils.getIsInAutomationFromEnvironment(intent)) {
- // We can't show the first run experience until Gecko has finished initialization (bug 1077583).
- mOnboardingHelper.checkFirstRun();
+ if (mTorNeedsStart) {
+ showTorBootstrapPager();
+ } else {
+ // We can't show the first run experience until Gecko has finished initialization (bug 1077583).
+ mOnboardingHelper.checkFirstRun();
+ }
}
}
@@ -2627,6 +2633,11 @@ public class BrowserApp extends GeckoApp
return (SplashScreen) splashLayout.findViewById(R.id.splash_root);
}
+ private boolean isTorBootstrapVisible() {
+ return (mTorBootstrapAnimationContainer != null && mTorBootstrapAnimationContainer.isVisible()
+ && mHomeScreenContainer != null && mHomeScreenContainer.getVisibility() == View.VISIBLE);
+ }
+
/**
* Enters editing mode with the current tab's URL. There might be no
* tabs loaded by the time the user enters editing mode e.g. just after
@@ -2988,6 +2999,37 @@ public class BrowserApp extends GeckoApp
}
}
+ private void showTorBootstrapPager() {
+
+ if (mTorBootstrapAnimationContainer == null) {
+ // We can't use toggleToolbarChrome() because that uses INVISIBLE, but we need GONE
+ mBrowserChrome.setVisibility(View.GONE);
+ final ViewStub torBootstrapPagerStub = (ViewStub) findViewById(R.id.tor_bootstrap_pager_stub);
+ mTorBootstrapAnimationContainer = (TorBootstrapAnimationContainer) torBootstrapPagerStub.inflate();
+ mTorBootstrapAnimationContainer.load(this, getSupportFragmentManager());
+ mTorBootstrapAnimationContainer.registerOnFinishListener(new TorBootstrapAnimationContainer.OnFinishListener() {
+ @Override
+ public void onFinish() {
+ // Show the chrome again
+ toggleToolbarChrome(true);
+ // When the content loaded in the background (such as about:tor),
+ // it was loaded while mBrowserChrome was GONE. We should refresh the
+ // height now so the page is rendered correctly.
+ Tabs.getInstance().getSelectedTab().doReload(true);
+
+ // If we finished, then Tor bootstrapped 100%
+ mTorNeedsStart = false;
+
+ // When bootstrapping completes, check if the Firstrun (onboarding) screens
+ // should be shown.
+ mOnboardingHelper.checkFirstRun();
+ }
+ });
+ }
+
+ mHomeScreenContainer.setVisibility(View.VISIBLE);
+ }
+
private void showHomePager(String panelId, Bundle panelRestoreData) {
showHomePagerWithAnimator(panelId, panelRestoreData, null);
}
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapAnimationContainer.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapAnimationContainer.java
new file mode 100644
index 000000000000..188e03df0092
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapAnimationContainer.java
@@ -0,0 +1,82 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.torbootstrap;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.v4.app.FragmentManager;
+import android.util.AttributeSet;
+
+import android.view.View;
+import android.widget.LinearLayout;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
+
+/**
+ * A container for the bootstrapping flow.
+ *
+ * Mostly a modified version of FirstrunAnimationContainer
+ */
+public class TorBootstrapAnimationContainer extends FirstrunAnimationContainer {
+
+ public static interface OnFinishListener {
+ public void onFinish();
+ }
+
+ private TorBootstrapPager pager;
+ private boolean visible;
+
+ // Provides a callback so BrowserApp can execute an action
+ // when the bootstrapping is complete and the bootstrapping
+ // screen closes.
+ private OnFinishListener onFinishListener;
+
+ public TorBootstrapAnimationContainer(Context context) {
+ this(context, null);
+ }
+ public TorBootstrapAnimationContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void load(Activity activity, FragmentManager fm) {
+ visible = true;
+ pager = findViewById(R.id.tor_bootstrap_pager);
+ pager.load(activity, fm, new OnFinishListener() {
+ @Override
+ public void onFinish() {
+ hide();
+ }
+ });
+ }
+
+ public void hide() {
+ visible = false;
+ if (onFinishListener != null) {
+ onFinishListener.onFinish();
+ }
+ animateHide();
+ }
+
+ private void animateHide() {
+ final Animator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 0);
+ alphaAnimator.setDuration(150);
+ alphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ TorBootstrapAnimationContainer.this.setVisibility(View.GONE);
+ }
+ });
+
+ alphaAnimator.start();
+ }
+
+ public void registerOnFinishListener(OnFinishListener listener) {
+ onFinishListener = listener;
+ }
+}
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapLogPanel.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapLogPanel.java
new file mode 100644
index 000000000000..18d827cec216
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapLogPanel.java
@@ -0,0 +1,54 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.torbootstrap;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import org.mozilla.gecko.R;
+
+/**
+ * Simple subclass of TorBootstrapPanel specifically for showing
+ * Tor and Orbot log entries.
+ */
+public class TorBootstrapLogPanel extends TorBootstrapPanel {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
+ mRoot = (ViewGroup) inflater.inflate(R.layout.tor_bootstrap_log, container, false);
+
+ if (mRoot == null) {
+ Log.w(LOGTAG, "Inflating R.layout.tor_bootstrap returned null");
+ return null;
+ }
+
+ TorLogEventListener.addLogger(this);
+
+ return mRoot;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstance) {
+ super.onViewCreated(view, savedInstance);
+ // Inherited from the super class
+ configureGearCogClickHandler();
+ }
+
+ // TODO Add a button for Go-to-bottom
+ @Override
+ public void updateStatus(String torServiceMsg, String newTorStatus) {
+ if (torServiceMsg == null) {
+ return;
+ }
+ TextView torLog = (TextView) mRoot.findViewById(R.id.tor_bootstrap_last_status_message);
+ torLog.append("- " + torServiceMsg + "\n");
+ }
+}
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapLogger.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapLogger.java
new file mode 100644
index 000000000000..24c9321beb63
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapLogger.java
@@ -0,0 +1,17 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.torbootstrap;
+
+import android.app.Activity;
+
+// Simple interface for a logger.
+//
+// The current implementers are TorBootstrapPanel and
+// TorBootstrapLogPanel.
+public interface TorBootstrapLogger {
+ public void updateStatus(String torServiceMsg, String newTorStatus);
+ public Activity getActivity();
+}
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPager.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPager.java
new file mode 100644
index 000000000000..587806791e52
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPager.java
@@ -0,0 +1,203 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.torbootstrap;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+
+import org.mozilla.gecko.firstrun.FirstrunPager;
+
+import java.util.List;
+
+/**
+ * ViewPager containing our bootstrapping pages.
+ *
+ * Based on FirstrunPager for simplicity
+ */
+public class TorBootstrapPager extends FirstrunPager {
+
+ private Context context;
+ private Activity mActivity;
+ protected TorBootstrapPanel.PagerNavigation pagerNavigation;
+
+ public TorBootstrapPager(Context context) {
+ this(context, null);
+ }
+
+ public TorBootstrapPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ super.addView(child, index, params);
+ }
+
+ // Load the default (hard-coded) panels from TorBootstrapPagerConfig
+ // Mostly copied from super
+ public void load(Activity activity, FragmentManager fm, final TorBootstrapAnimationContainer.OnFinishListener onFinishListener) {
+ mActivity = activity;
+ final List<TorBootstrapPagerConfig.TorBootstrapPanelConfig> panels = TorBootstrapPagerConfig.getDefaultBootstrapPanel();
+
+ this.pagerNavigation = new TorBootstrapPanel.PagerNavigation() {
+ @Override
+ public void next() {
+ // No-op implementation.
+ }
+
+ @Override
+ public void finish() {
+ if (onFinishListener != null) {
+ onFinishListener.onFinish();
+ }
+ }
+ };
+
+ ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(fm, panels);
+ setAdapter(viewPagerAdapter);
+
+ // The Fragments (Panels) should be attached to a parent View at this point (and
+ // the parent View should be |this|). If the Fragment's getParent() method returns
+ // |null|, then the Fragment was probably instantiated earlier by the FragmentManager
+ // (most likely because the app's state is being restored after it was killed by the
+ // system). If the parent View is not null, then the Fragment was instantiated below
+ // in the ViewPagerAdapter constructor.
+ //
+ // In the case where the Fragment's getParent() is null, then the Fragment was
+ // instantiated before TorBootstrapPager (|this|) was created. As a result, the
+ // fragment wasn't automatically added as a child View of the Pager (|this|) when it
+ // was created. Add the Fragments as children now.
+ //
+ // There may be a more Androidy-way of handling this.
+ for (int i = 0; i < viewPagerAdapter.getCount(); i++) {
+ Fragment fragment = viewPagerAdapter.getItem(i);
+ if (fragment == null) {
+ continue;
+ }
+
+ View fragmentView = fragment.getView();
+ if (fragmentView == null) {
+ continue;
+ }
+
+ if (fragmentView.getParent() == null) {
+ addView(fragmentView);
+ }
+ }
+
+ animateLoad();
+ }
+
+ // Copied from super
+ private void animateLoad() {
+ setTranslationY(500);
+ setAlpha(0);
+
+ final Animator translateAnimator = ObjectAnimator.ofFloat(this, "translationY", 0);
+ translateAnimator.setDuration(400);
+
+ final Animator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 1);
+ alphaAnimator.setStartDelay(200);
+ alphaAnimator.setDuration(600);
+
+ final AnimatorSet set = new AnimatorSet();
+ set.playTogether(alphaAnimator, translateAnimator);
+ set.setStartDelay(400);
+
+ set.start();
+ }
+
+ // Provide an interface for inter-panel communication allowing
+ // the logging panel to stop the bootstrapping animation on the
+ // main panel.
+ public interface TorBootstrapController {
+ void startBootstrapping();
+ void stopBootstrapping();
+ }
+
+ // Mostly copied from FirstrunPager
+ protected class ViewPagerAdapter extends FragmentPagerAdapter implements TorBootstrapController {
+ private final List<TorBootstrapPagerConfig.TorBootstrapPanelConfig> panels;
+ private final Fragment[] fragments;
+
+ public ViewPagerAdapter(FragmentManager fm, List<TorBootstrapPagerConfig.TorBootstrapPanelConfig> panels) {
+ super(fm);
+ this.panels = panels;
+ this.fragments = getPagerPanels(fm);
+ }
+
+ private Fragment[] getPagerPanels(FragmentManager fm) {
+ Fragment[] fragments = new Fragment[panels.size()];
+ for (int i = 0; i < fragments.length; i++) {
+ TorBootstrapPagerConfig.TorBootstrapPanelConfig panelConfig = panels.get(i);
+
+ // Fragment tag is created as "android:switcher:" + viewId + ":" + id
+ // where |viewId| is the ID of the parent View container (in this case
+ // TorBootstrapPager is the parent View of the panels), and |id| is the
+ // position within the pager (in this case, it is |i| here)
+ // https://android.googlesource.com/platform/frameworks/support/+/refs/heads/m…
+ String fragmentTag = "android:switcher:" + TorBootstrapPager.this.getId() + ":" + i;
+
+ // If the Activity is being restored, then find the existing fragment. If the
+ // fragment doesn't exist, then instantiate it.
+ fragments[i] = fm.findFragmentByTag(fragmentTag);
+ if (fragments[i] == null) {
+ // We know the class is within the "org.mozilla.gecko.torbootstrap" package namespace
+ fragments[i] = Fragment.instantiate(mActivity.getApplicationContext(), panelConfig.getClassname());
+ }
+
+ ((TorBootstrapPanel) fragments[i]).setPagerNavigation(pagerNavigation);
+ ((TorBootstrapPanel) fragments[i]).setContext(mActivity);
+ ((TorBootstrapPanel) fragments[i]).setBootstrapController(this);
+ }
+ return fragments;
+ }
+
+ @Override
+ public Fragment getItem(int i) {
+ return fragments[i];
+ }
+
+ @Override
+ public int getCount() {
+ return panels.size();
+ }
+
+ public void startBootstrapping() {
+ if (fragments.length == 0) {
+ return;
+ }
+
+ TorBootstrapPanel mainPanel = (TorBootstrapPanel) getItem(0);
+ if (mainPanel == null) {
+ return;
+ }
+ mainPanel.startBootstrapping();
+ }
+
+ public void stopBootstrapping() {
+ if (fragments.length == 0) {
+ return;
+ }
+
+ TorBootstrapPanel mainPanel = (TorBootstrapPanel) getItem(0);
+ if (mainPanel == null) {
+ return;
+ }
+ mainPanel.stopBootstrapping();
+ }
+ }
+}
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPagerConfig.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPagerConfig.java
new file mode 100644
index 000000000000..17454da91444
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPagerConfig.java
@@ -0,0 +1,48 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.torbootstrap;
+
+import android.util.Log;
+import org.mozilla.gecko.GeckoSharedPrefs;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class TorBootstrapPagerConfig {
+ public static final String LOGTAG = "TorBootstrapPagerConfig";
+
+ public static final String KEY_IMAGE = "imageRes";
+ public static final String KEY_TEXT = "textRes";
+ public static final String KEY_SUBTEXT = "subtextRes";
+ public static final String KEY_CTATEXT = "ctatextRes";
+
+ public static List<TorBootstrapPanelConfig> getDefaultBootstrapPanel() {
+ final List<TorBootstrapPanelConfig> panels = new LinkedList<>();
+ panels.add(SimplePanelConfigs.bootstrapPanelConfig);
+ panels.add(SimplePanelConfigs.torLogPanelConfig);
+
+ return panels;
+ }
+
+ public static class TorBootstrapPanelConfig {
+
+ private String classname;
+
+ public TorBootstrapPanelConfig(String classname) {
+ this.classname = classname;
+ }
+
+ public String getClassname() {
+ return this.classname;
+ }
+ }
+
+ private static class SimplePanelConfigs {
+ public static final TorBootstrapPanelConfig bootstrapPanelConfig = new TorBootstrapPanelConfig(TorBootstrapPanel.class.getName());
+ public static final TorBootstrapPanelConfig torLogPanelConfig = new TorBootstrapPanelConfig(TorBootstrapLogPanel.class.getName());
+
+ }
+}
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPanel.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPanel.java
new file mode 100644
index 000000000000..54b1c41b1a9f
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPanel.java
@@ -0,0 +1,575 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.torbootstrap;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.util.Log;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.firstrun.FirstrunPanel;
+
+import org.torproject.android.service.OrbotConstants;
+import org.torproject.android.service.TorService;
+import org.torproject.android.service.TorServiceConstants;
+import org.torproject.android.service.util.TorServiceUtils;
+
+
+/**
+ * Tor Bootstrap panel (fragment/screen)
+ *
+ * This is based on the Firstrun Panel for simplicity.
+ */
+public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogger {
+
+ protected static final String LOGTAG = "TorBootstrap";
+
+ protected ViewGroup mRoot;
+ protected Activity mActContext;
+ protected TorBootstrapPager.TorBootstrapController mBootstrapController;
+
+ private ViewTreeLayoutListener mViewTreeLayoutListener;
+
+ // These are used by the background AlphaChanging thread for dynamically changing
+ // the alpha value of the Onion during bootstrap.
+ private int mOnionCurrentAlpha = 255;
+ // This is either +1 or -1, depending on the direction of the change.
+ private int mOnionCurrentAlphaDirection = -1;
+ private Object mOnionAlphaChangerLock = new Object();
+ private boolean mOnionAlphaChangerRunning = false;
+
+ // Runnable for changing the alpha of the Onion image every 100 milliseconds.
+ // It gradually increases and then decreases the alpha in the background and
+ // then applies the new alpha on the UI thread.
+ private Thread mChangeOnionAlphaThread = null;
+ final private class ChangeOnionAlphaRunnable implements Runnable {
+ @Override
+ public void run() {
+ while (true) {
+ synchronized(mOnionAlphaChangerLock) {
+ // Stop the animation and terminate this thread if the main thread
+ // set |mOnionAlphaChangerRunning| to |false| or if
+ // getActivity() returns |null|.
+ if (!mOnionAlphaChangerRunning || getActivity() == null) {
+ // Null the reference for this thread when we exit
+ mChangeOnionAlphaThread = null;
+ return;
+ }
+ }
+
+ // Choose the new value here, mOnionCurrentAlpha is set in setOnionAlphaValue()
+ // Increase by 5 if mOnionCurrentAlphaDirection is positive, and decrease by
+ // 5 if mOnionCurrentAlphaDirection is negative.
+ final int newAlpha = mOnionCurrentAlpha + mOnionCurrentAlphaDirection*5;
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ setOnionAlphaValue(newAlpha);
+ }
+ });
+
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {}
+ }
+ }
+ }
+
+ // Android tries scaling the image as a square. Create a modified ViewPort via padding
+ // top, left, right, and bottom such that the image aspect ratio is correct.
+ private void setOnionImgLayout() {
+ if (mRoot == null) {
+ Log.i(LOGTAG, "setOnionImgLayout: mRoot is null");
+ return;
+ }
+
+ ImageView onionImg = (ImageView) mRoot.findViewById(R.id.tor_bootstrap_onion);
+ if (onionImg == null) {
+ Log.i(LOGTAG, "setOnionImgLayout: onionImg is null");
+ return;
+ }
+
+ // Dimensions of the SVG. If the image is ever changed, update these values. The
+ // SVG viewport is 2dp wider due to clipping.
+ final double imgHeight = 289.;
+ final double imgWidth = 247.;
+
+ // Dimensions of the current ImageView
+ final int currentHeight = onionImg.getHeight();
+ final int currentWidth = onionImg.getWidth();
+
+ // If we only consider one dimension of the image, calculate the expected value
+ // of the other dimension (width vs. height).
+ final int expectedHeight = (int) (currentWidth*imgHeight/imgWidth);
+ final int expectedWidth = (int) (currentHeight*imgWidth/imgHeight);
+
+ // Set current values as default.
+ int newWidth = currentWidth;
+ int newHeight = currentHeight;
+
+ Log.d(LOGTAG, "Current Top=" + onionImg.getTop());
+ Log.d(LOGTAG, "Current Height=" + currentHeight);
+ Log.d(LOGTAG, "Current Width=" + currentWidth);
+ Log.d(LOGTAG, "Expected height=" + expectedHeight);
+ Log.d(LOGTAG, "Expected width=" + expectedWidth);
+
+ // Configure the width or height based on its expected value. This is based on
+ // the intuition that:
+ // - If the device is in portrait mode, then the device's height is (likely)
+ // greater than its width. When this is the case, then:
+ // - The image's View object is likely using all available vertical area
+ // (but the image is bounded by the width of the device due to
+ // maintaining the scaling factor).
+ // - However, the height and width of the graphic are equal (because
+ // Android enforces this).
+ // - The width should be less than the height (this is a property of
+ // the image itself).
+ // - The width should be proportional to the imgHeight and imgWidth
+ // defined above.
+ // Adjust the height when the current width is less than the expected width.
+ // The width is the limiting-factor, therefore choose the height proportional
+ // to the current width.
+ //
+ // - The opposite is likely true when the device is in landscape mode with
+ // respect to the height and width. Adjust the width when the height is less
+ // than the expected height. The height is the limiting-factor, therefore
+ // choose the width proportional to the current height.
+ //
+ // Subtract 1 from the expected value as a way of accounting for rounding
+ // error.
+ if (currentWidth < (expectedWidth - 1)) {
+ newHeight = expectedHeight;
+ } else if (currentHeight < (expectedHeight - 1)) {
+ newWidth = expectedWidth;
+ }
+
+ Log.d(LOGTAG, "New height=" + newHeight);
+ Log.d(LOGTAG, "New width=" + newWidth);
+
+ // Define the padding as the available space between the current height (as it
+ // is displayed to the user) and the new height (as it was calculated above).
+ int verticalPadding = currentHeight - newHeight;
+ int sidePadding = currentWidth - newWidth;
+ int leftPadding = 0;
+ int topPadding = 0;
+ int bottomPadding = 0;
+ int rightPadding = 0;
+
+ // If the width of the image is greater than 600dp, then cap it at 702x600 (HxW).
+ // Furthermore, if the width is "near" 600dp (within 100dp), then decrease the
+ // dimensions to 468x400 dp. This should "look" better on lower-resolution
+ // devices.
+ final int MAXIMUM_WIDTH = 600;
+ final int distanceFromMaxWidth = newWidth - MAXIMUM_WIDTH;
+ final boolean isNearMaxWidth = Math.abs(distanceFromMaxWidth) < 100;
+ if ((newWidth > MAXIMUM_WIDTH) || isNearMaxWidth) {
+ if (isNearMaxWidth) {
+ // If newWidth is near MAX_WIDTH, then add additional padding (therefore
+ // decreasing the width by an additional 200dp).
+ sidePadding += 200;
+ }
+
+ final int paddingSpaceAvailable = (distanceFromMaxWidth > 0) ? distanceFromMaxWidth : 0;
+ sidePadding += paddingSpaceAvailable;
+
+ final int newWidthWithoutPadding = currentWidth - sidePadding;
+
+ final int newHeightWithoutPadding = (int) (newWidthWithoutPadding*imgHeight/imgWidth);
+
+ Log.d(LOGTAG, "New width without padding=" + newWidthWithoutPadding);
+ Log.d(LOGTAG, "New height without padding=" + newHeightWithoutPadding);
+
+ verticalPadding = currentHeight - newHeightWithoutPadding;
+ }
+
+ Log.d(LOGTAG, "New top padding=" + verticalPadding);
+ Log.d(LOGTAG, "New side padding=" + sidePadding);
+
+ if (verticalPadding < 0) {
+ Log.i(LOGTAG, "vertical padding is " + verticalPadding);
+ verticalPadding = 0;
+ } else {
+ // Place 4/5 of padding at top, and 1/5 of padding at bottom.
+ topPadding = (verticalPadding*4)/5;
+ bottomPadding = verticalPadding/5;
+ }
+
+ if (sidePadding < 0) {
+ Log.i(LOGTAG, "side padding is " + sidePadding);
+ leftPadding = 0;
+ rightPadding = 0;
+ } else {
+ // Divide the padding equally on the left and right side.
+ leftPadding = sidePadding/2;
+ rightPadding = leftPadding;
+ }
+
+ // Create a padding-box around the image and let Android fill the box with
+ // the image. Android will scale the width and height independently, so the
+ // end result should be a correctly-sized graphic.
+ onionImg.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
+
+ // Separately scale x- and y-dimension.
+ onionImg.setScaleType(ImageView.ScaleType.FIT_XY);
+
+ // Invalidate the view because the image disappears (is not redrawn) sometimes when
+ // the screen is rotated.
+ onionImg.invalidate();
+ }
+
+ private class ViewTreeLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
+ @Override
+ public void onGlobalLayout() {
+ TorBootstrapPanel.this.setOnionImgLayout();
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
+ mRoot = (ViewGroup) inflater.inflate(R.layout.tor_bootstrap, container, false);
+ if (mRoot == null) {
+ Log.w(LOGTAG, "Inflating R.layout.tor_bootstrap returned null");
+ return null;
+ }
+
+ Button connectButton = mRoot.findViewById(R.id.tor_bootstrap_connect);
+ if (connectButton == null) {
+ Log.w(LOGTAG, "Finding the Connect button failed. Did the ID change?");
+ return null;
+ }
+
+ connectButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startBootstrapping();
+ }
+ });
+
+ if (Build.VERSION.SDK_INT > 20) {
+ // Round the button's edges, but only on API 21+. Earlier versions
+ // do not support this.
+ //
+ // This should be declared in the xml layout, however there is a bug
+ // preventing this (the XML attribute isn't actually defined in the
+ // SDK).
+ // https://issuetracker.google.com/issues/37036728
+ connectButton.setClipToOutline(true);
+ }
+
+ configureGearCogClickHandler();
+
+ TorLogEventListener.addLogger(this);
+
+ // Add a callback for notification when the layout is complete and all components
+ // are measured. Waiting until the layout is complete is necessary before we correctly
+ // set the size of the onion. Cache the listener so we may remove it later.
+ mViewTreeLayoutListener = new ViewTreeLayoutListener();
+ mRoot.getViewTreeObserver().addOnGlobalLayoutListener(mViewTreeLayoutListener);
+
+ return mRoot;
+ }
+
+ @Override
+ public void onDestroyView() {
+ // Inform the background AlphaChanging thread it should terminate.
+ synchronized(mOnionAlphaChangerLock) {
+ mOnionAlphaChangerRunning = false;
+ }
+
+ super.onDestroyView();
+ }
+
+ private void setOnionAlphaValue(int newAlpha) {
+ ImageView onionImg = (ImageView) mRoot.findViewById(R.id.tor_bootstrap_onion);
+ if (onionImg == null) {
+ return;
+ }
+
+ if (newAlpha > 255) {
+ // Cap this at 255 and change direction of animation
+ newAlpha = 255;
+
+ synchronized(mOnionAlphaChangerLock) {
+ mOnionCurrentAlphaDirection = -1;
+ }
+ } else if (newAlpha < 0) {
+ // Lower-bound this at 0 and change direction of animation
+ newAlpha = 0;
+
+ synchronized(mOnionAlphaChangerLock) {
+ mOnionCurrentAlphaDirection = 1;
+ }
+ }
+ onionImg.setImageAlpha(newAlpha);
+ mOnionCurrentAlpha = newAlpha;
+ }
+
+ public void updateStatus(String torServiceMsg, String newTorStatus) {
+ final String noticePrefix = "NOTICE: ";
+
+ if (torServiceMsg == null) {
+ return;
+ }
+
+ TextView torLog = (TextView) mRoot.findViewById(R.id.tor_bootstrap_last_status_message);
+ if (torLog == null) {
+ Log.w(LOGTAG, "updateStatus: torLog is null?");
+ }
+ // Only show Notice-level log messages on this panel
+ if (torServiceMsg.startsWith(noticePrefix)) {
+ // Drop the prefix
+ String msg = torServiceMsg.substring(noticePrefix.length());
+ torLog.setText(msg);
+ } else if (torServiceMsg.toLowerCase().contains("error")) {
+ torLog.setText(R.string.tor_notify_user_about_error);
+
+ // This may be a false-positive, but if we encountered an error within
+ // the OrbotService then there's likely nothing the user can do. This
+ // isn't persistent, so if they restart the app the button will be
+ // visible again.
+ Button connectButton = mRoot.findViewById(R.id.tor_bootstrap_connect);
+ if (connectButton == null) {
+ Log.w(LOGTAG, "updateStatus: Finding the Connect button failed. Did the ID change?");
+ } else {
+ TextView swipeLeftLog = (TextView) mRoot.findViewById(R.id.tor_bootstrap_swipe_log);
+ if (swipeLeftLog == null) {
+ Log.w(LOGTAG, "updateStatus: swipeLeftLog is null?");
+ }
+
+ // Abuse this by showing the log message despite not bootstrapping
+ toggleVisibleElements(true, torLog, connectButton, swipeLeftLog);
+ }
+ }
+
+ // Return to the browser when we reach 100% bootstrapped
+ if (torServiceMsg.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
+ // Inform the background AlphaChanging thread it should terminate
+ synchronized(mOnionAlphaChangerLock) {
+ mOnionAlphaChangerRunning = false;
+ }
+ close();
+
+ // Remove the listener when we're done
+ mRoot.getViewTreeObserver().removeOnGlobalLayoutListener(mViewTreeLayoutListener);
+ }
+ }
+
+ public void setContext(Activity ctx) {
+ mActContext = ctx;
+ }
+
+ // Save the TorBootstrapController.
+ // This method won't be used by the main TorBootstrapPanel (|this|), but
+ // it will be used by its childen.
+ public void setBootstrapController(TorBootstrapPager.TorBootstrapController bootstrapController) {
+ mBootstrapController = bootstrapController;
+ }
+
+ private void startTorService() {
+ Intent torService = new Intent(getActivity(), TorService.class);
+ torService.setAction(TorServiceConstants.ACTION_START);
+ getActivity().startService(torService);
+ }
+
+ private void stopTorService() {
+ // First, stop the current bootstrapping process (if it's in progress)
+ // TODO Ideally, we'd DisableNetwork here, but that's not available.
+ Intent torService = new Intent(getActivity(), TorService.class);
+ getActivity().stopService(torService);
+ }
+
+ // Setup OnClick handler for the settings gear/cog
+ protected void configureGearCogClickHandler() {
+ if (mRoot == null) {
+ Log.w(LOGTAG, "configureGearCogClickHandler: mRoot is null?");
+ return;
+ }
+
+ final ImageView gearSettingsImage = mRoot.findViewById(R.id.tor_bootstrap_settings_gear);
+ if (gearSettingsImage == null) {
+ Log.w(LOGTAG, "configureGearCogClickHandler: gearSettingsImage is null?");
+ return;
+ }
+
+ gearSettingsImage.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // The existance of the connect button is an indicator of the user
+ // interacting with the main bootstrapping screen or the loggin screen.
+ Button connectButton = mRoot.findViewById(R.id.tor_bootstrap_connect);
+ if (connectButton == null) {
+ Log.w(LOGTAG, "gearSettingsImage onClick: Finding the Connect button failed, proxying request.");
+
+ // If there isn't a connect button on this screen, then proxy the
+ // stopBootstrapping() request via the TorBootstrapController (which
+ // is the underlying PagerAdapter).
+ mBootstrapController.stopBootstrapping();
+ } else {
+ stopBootstrapping();
+ }
+ // Open Tor Network Settings preferences screen
+ Intent intent = new Intent(mActContext, TorPreferences.class);
+ mActContext.startActivity(intent);
+ }
+ });
+ }
+
+ private void toggleVisibleElements(boolean bootstrapping, TextView lastStatus, Button connect, TextView swipeLeft) {
+ final int connectVisible = bootstrapping ? View.INVISIBLE : View.VISIBLE;
+ final int infoTextVisible = bootstrapping ? View.VISIBLE : View.INVISIBLE;
+
+ if (connect != null) {
+ connect.setVisibility(connectVisible);
+ }
+ if (lastStatus != null) {
+ lastStatus.setVisibility(infoTextVisible);
+ }
+ if (swipeLeft != null) {
+ swipeLeft.setVisibility(infoTextVisible);
+ }
+ }
+
+ private void startBackgroundAlphaChangingThread() {
+ // If it is non-null, then this is a bug because the thread should null this reference when
+ // it terminates.
+ if (mChangeOnionAlphaThread != null) {
+ if (mChangeOnionAlphaThread.getState() == Thread.State.TERMINATED) {
+ // The thread likely terminated unexpectedly, null the reference.
+ // The thread should set this itself.
+ Log.i(LOGTAG, "mChangeOnionAlphaThread.getState(): is terminated");
+ mChangeOnionAlphaThread = null;
+ } else {
+ // The reference is not nulled in this case because another
+ // background thread would start otherwise. The thread is currently in
+ // an unknown state, simply set the Running flag as false.
+ Log.w(LOGTAG, "We're in an unexpected state. mChangeOnionAlphaThread.getState(): " + mChangeOnionAlphaThread.getState());
+
+ synchronized(mOnionAlphaChangerLock) {
+ mOnionAlphaChangerRunning = false;
+ }
+ }
+ }
+
+ // If the background thread is not currently running, then start it.
+ if (mChangeOnionAlphaThread == null) {
+ mChangeOnionAlphaThread = new Thread(new ChangeOnionAlphaRunnable());
+ if (mChangeOnionAlphaThread == null) {
+ Log.w(LOGTAG, "Instantiating a new ChangeOnionAlphaRunnable Thread failed.");
+ } else if (mChangeOnionAlphaThread.getState() == Thread.State.NEW) {
+ Log.i(LOGTAG, "Starting mChangeOnionAlphaThread");
+
+ // Synchronization across threads should not be necessary because there
+ // shouldn't be any other threads relying on mOnionAlphaChangerRunning.
+ // Do this purely for safety.
+ synchronized(mOnionAlphaChangerLock) {
+ mOnionAlphaChangerRunning = true;
+ }
+
+ mChangeOnionAlphaThread.start();
+ }
+ }
+ }
+
+ public void startBootstrapping() {
+ if (mRoot == null) {
+ Log.w(LOGTAG, "startBootstrapping: mRoot is null?");
+ return;
+ }
+ // Start bootstrap process and transition into the bootstrapping-tor-panel
+ Button connectButton = mRoot.findViewById(R.id.tor_bootstrap_connect);
+ if (connectButton == null) {
+ Log.w(LOGTAG, "startBootstrapping: connectButton is null?");
+ return;
+ }
+
+ ImageView onionImg = (ImageView) mRoot.findViewById(R.id.tor_bootstrap_onion);
+
+ Drawable drawableOnion = onionImg.getDrawable();
+
+ mOnionCurrentAlpha = 255;
+ // The onion should have 100% alpha, begin decreasing it.
+ mOnionCurrentAlphaDirection = -1;
+ startBackgroundAlphaChangingThread();
+
+ TextView torStatus = (TextView) mRoot.findViewById(R.id.tor_bootstrap_last_status_message);
+ if (torStatus == null) {
+ Log.w(LOGTAG, "startBootstrapping: torStatus is null?");
+ return;
+ }
+
+ TextView swipeLeftLog = (TextView) mRoot.findViewById(R.id.tor_bootstrap_swipe_log);
+ if (swipeLeftLog == null) {
+ Log.w(LOGTAG, "startBootstrapping: swipeLeftLog is null?");
+ return;
+ }
+
+ torStatus.setText(getString(R.string.tor_bootstrap_starting_status));
+
+ toggleVisibleElements(true, torStatus, connectButton, swipeLeftLog);
+ startTorService();
+ }
+
+ // This is public because this Pager may call this method if another Panel requests it.
+ public void stopBootstrapping() {
+ if (mRoot == null) {
+ Log.w(LOGTAG, "stopBootstrapping: mRoot is null?");
+ return;
+ }
+ // Transition from the animated bootstrapping panel to
+ // the static "Connect" panel
+ Button connectButton = mRoot.findViewById(R.id.tor_bootstrap_connect);
+ if (connectButton == null) {
+ Log.w(LOGTAG, "stopBootstrapping: connectButton is null?");
+ return;
+ }
+
+ ImageView onionImg = (ImageView) mRoot.findViewById(R.id.tor_bootstrap_onion);
+ if (onionImg == null) {
+ Log.w(LOGTAG, "stopBootstrapping: onionImg is null?");
+ return;
+ }
+
+ // Inform the background AlphaChanging thread it should terminate.
+ synchronized(mOnionAlphaChangerLock) {
+ mOnionAlphaChangerRunning = false;
+ }
+
+ Drawable drawableOnion = onionImg.getDrawable();
+
+ // Reset the onion's alpha value.
+ onionImg.setImageAlpha(255);
+
+ TextView torStatus = (TextView) mRoot.findViewById(R.id.tor_bootstrap_last_status_message);
+ if (torStatus == null) {
+ Log.w(LOGTAG, "stopBootstrapping: torStatus is null?");
+ return;
+ }
+
+ TextView swipeLeftLog = (TextView) mRoot.findViewById(R.id.tor_bootstrap_swipe_log);
+ if (swipeLeftLog == null) {
+ Log.w(LOGTAG, "stopBootstrapping: swipeLeftLog is null?");
+ return;
+ }
+
+ // Reset the displayed message
+ torStatus.setText("");
+
+ toggleVisibleElements(false, torStatus, connectButton, swipeLeftLog);
+ stopTorService();
+ }
+}
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorLogEventListener.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorLogEventListener.java
new file mode 100644
index 000000000000..6218763475e5
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorLogEventListener.java
@@ -0,0 +1,128 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.torbootstrap;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.content.LocalBroadcastManager;
+
+import org.torproject.android.service.OrbotConstants;
+import org.torproject.android.service.TorService;
+import org.torproject.android.service.TorServiceConstants;
+import org.torproject.android.service.util.TorServiceUtils;
+
+import java.util.Vector;
+
+
+/**
+ * This is simply a container for capturing the log events and proxying them
+ * to the TorBootstrapLogger implementers (TorBootstrapPanel and TorBootstrapLogPanel now).
+ *
+ * This should be in BrowserApp, but that class/Activity is already too large,
+ * so this should be easier to reason about.
+ */
+public class TorLogEventListener {
+
+ private static Vector<TorBootstrapLogger> mLoggers;
+
+ private TorLogEventListener instance;
+ private static boolean isInitialized = false;
+
+ public TorLogEventListener getInstance(Context context) {
+ if (instance == null) {
+ instance = new TorLogEventListener();
+ }
+ return instance;
+ }
+
+ private synchronized static void initialize(Context context) {
+ LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
+ lbm.registerReceiver(mLocalBroadcastReceiver,
+ new IntentFilter(TorServiceConstants.ACTION_STATUS));
+ lbm.registerReceiver(mLocalBroadcastReceiver,
+ new IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG));
+
+ isInitialized = true;
+ // There should be at least two Loggers: TorBootstrapPanel
+ // and TorBootstrapLogPanel
+ mLoggers = new Vector<TorBootstrapLogger>(2);
+ }
+
+ public synchronized static void addLogger(TorBootstrapLogger logger) {
+ if (!isInitialized) {
+ // This is an assumption we're making. All Loggers are a subclass
+ // of an Activity.
+ Activity activity = logger.getActivity();
+ initialize(activity);
+ }
+
+ if (mLoggers.contains(logger)) {
+ return;
+ }
+ mLoggers.add(logger);
+ }
+
+ public synchronized static void deleteLogger(TorBootstrapLogger logger) {
+ mLoggers.remove(logger);
+ }
+
+ /**
+ * The state and log info from {@link TorService} are sent to the UI here in
+ * the form of a local broadcast. Regular broadcasts can be sent by any app,
+ * so local ones are used here so other apps cannot interfere with Orbot's
+ * operation.
+ *
+ * Copied from Orbot - OrbotMainActivity.java
+ */
+ private static BroadcastReceiver mLocalBroadcastReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ // This is only defined for log updates
+ if (!action.equals(TorServiceConstants.LOCAL_ACTION_LOG) &&
+ !action.equals(TorServiceConstants.ACTION_STATUS)) {
+ return;
+ }
+
+ Message msg = mStatusUpdateHandler.obtainMessage();
+
+ if (action.equals(TorServiceConstants.LOCAL_ACTION_LOG)) {
+ msg.obj = intent.getStringExtra(TorServiceConstants.LOCAL_EXTRA_LOG);
+ }
+
+ msg.getData().putString("status",
+ intent.getStringExtra(TorServiceConstants.EXTRA_STATUS));
+ mStatusUpdateHandler.sendMessage(msg);
+ }
+ };
+
+
+ // this is what takes messages or values from the callback threads or other non-mainUI threads
+ // and passes them back into the main UI thread for display to the user
+ private static Handler mStatusUpdateHandler = new Handler() {
+
+ @Override
+ public void handleMessage(final Message msg) {
+ String newTorStatus = msg.getData().getString("status");
+ String log = (String)msg.obj;
+
+ for (TorBootstrapLogger l : mLoggers) {
+ l.updateStatus(log, newTorStatus);
+ }
+ super.handleMessage(msg);
+ }
+ };
+}
diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java
new file mode 100644
index 000000000000..9e74c49f3f91
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java
@@ -0,0 +1,975 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.torbootstrap;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.support.v7.app.ActionBar;
+import android.text.style.ClickableSpan;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.AdapterView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Vector;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.preferences.AppCompatPreferenceActivity;
+
+import org.torproject.android.service.util.Prefs;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import static org.mozilla.gecko.preferences.GeckoPreferences.NON_PREF_PREFIX;
+
+
+/** TorPreferences provides the Tor-related preferences
+ *
+ * We configure bridges using either a set of built-in bridges where the user enables
+ * them based on bridge type (the name of the pluggable transport) or the user provides
+ * their own bridge (obtained from another person or BridgeDB, etc).
+ *
+ * This class (TorPreferences) is divided into multiple Fragments (screens). The first
+ * screen is where the user enables or disables Bridges. The second screen shows the
+ * user a list of built-in bridge types (obfs4, meek, etc) where they may select one of
+ * them. It shows a button they may press for providing their own bridge, as well. The
+ * third screen is where the user may provide (copy/paste) their own bridge.
+ *
+ * On the first screen, if bridges are currently enabled, then the switch/toggle is
+ * shown as enabled. In addition, the user is shown a message saying whether built-in or
+ * provided bridges are being used. There is a link, labeled "Change", where they
+ * transitioned to the appropriate screen for modifying the configuration if it is pressed.
+ *
+ * The second screen shows radio buttons for the built-in bridge types.
+ *
+ * The State of Bridges-Enabled:
+ * There are a few moving parts here, a higher-level description of how we expect this
+ * works, where "Enabled" is "Bridges Enabled", "Type" is "Bridge Type", and "Provided"
+ * is "Bridge Provided":
+ *
+ * We have five preferences:
+ * PREFS_BRIDGES_ENABLED
+ * PREFS_BRIDGES_TYPE
+ * PREFS_BRIDGES_PROVIDE
+ * pref_bridges_enabled (tor-android-service)
+ * pref_bridges_list (tor-android-service)
+ *
+ * These may be in following three end states where PREFS_BRIDGES_ENABLED and
+ * pref_bridges_enabled must always match, and pref_bridges_list must either match
+ * PREFS_BRIDGES_PROVIDE or contain type PREFS_BRIDGES_TYPE.
+ *
+ * PREFS_BRIDGES_ENABLED=false
+ * PREFS_BRIDGES_TYPE=null
+ * PREFS_BRIDGES_PROVIDE=null
+ * pref_bridges_enabled=false
+ * pref_bridges_list=null
+ *
+ * PREFS_BRIDGES_ENABLED=true
+ * PREFS_BRIDGES_TYPE=T1
+ * PREFS_BRIDGES_PROVIDE=null
+ * pref_bridges_enabled=true
+ * pref_bridges_list=T1
+ *
+ * PREFS_BRIDGES_ENABLED=true
+ * PREFS_BRIDGES_TYPE=null
+ * PREFS_BRIDGES_PROVIDE=X2
+ * pref_bridges_enabled=true
+ * pref_bridges_list=X2
+ *
+ * There are transition states where this is not consistent, for example when the
+ * "Bridges Enabled" switch is toggled but "Bridge Type" and "Bridge Provided" are null.
+ */
+
+public class TorPreferences extends AppCompatPreferenceActivity {
+ private static final String LOGTAG = "TorPreferences";
+
+ private static final String PREFS_BRIDGES_ENABLED = NON_PREF_PREFIX + "tor.bridges.enabled";
+ private static final String PREFS_BRIDGES_TYPE = NON_PREF_PREFIX + "tor.bridges.type";
+ private static final String PREFS_BRIDGES_PROVIDE = NON_PREF_PREFIX + "tor.bridges.provide";
+
+ private static final String[] sTorPreferenceFragments = {TorPreferences.TorNetworkBridgesEnabledPreference.class.getName(),
+ TorPreferences.TorNetworkBridgeSelectPreference.class.getName(),
+ TorPreferences.TorNetworkBridgeProvidePreference.class.getName()};
+ // Current displayed PreferenceFragment
+ private TorNetworkPreferenceFragment mFrag;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Begin with the first (Enable Bridges) fragment
+ getIntent().putExtra(EXTRA_SHOW_FRAGMENT, TorPreferences.TorNetworkBridgesEnabledPreference.class.getName());
+ getIntent().putExtra(EXTRA_NO_HEADERS, true);
+ super.onCreate(savedInstanceState);
+
+ mFrag = null;
+ }
+
+ // Save the current preference when the app is minimized or swiped away.
+ @Override
+ public void onStop() {
+ if (mFrag != null) {
+ mFrag.onSaveState();
+ }
+ super.onStop();
+ }
+
+ // This is needed because launching a fragment fails if this
+ // method doesn't return true.
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ for (String frag : sTorPreferenceFragments) {
+ if (fragmentName.equals(frag)) {
+ return true;
+ }
+ }
+ Log.i(LOGTAG, "isValidFragment(): Returning false (" + fragmentName + ")");
+ return false;
+ }
+
+ public void setFragment(TorNetworkPreferenceFragment frag) {
+ mFrag = frag;
+ }
+
+ // Save the preference when the user returns to the previous screen using
+ // the back button
+ @Override
+ public void onBackPressed() {
+ if (mFrag != null) {
+ mFrag.onSaveState();
+ }
+ super.onBackPressed();
+ }
+
+ // Control the behavior when the Up button (back button in top-left
+ // corner) is pressed. Save the current preference and return to the
+ // previous screen.
+ @Override
+ public boolean onNavigateUp() {
+ super.onNavigateUp();
+
+ if (mFrag == null) {
+ Log.w(LOGTAG, "onNavigateUp(): mFrag is null");
+ return false;
+ }
+
+ // Handle the user pressing the Up button in the same way as
+ // we handle them pressing the Back button. Strictly, this
+ // isn't correct, but it will prevent confusion.
+ mFrag.onSaveState();
+
+ if (mFrag.getFragmentManager().getBackStackEntryCount() > 0) {
+ Log.i(LOGTAG, "onNavigateUp(): popping from backstatck");
+ mFrag.getFragmentManager().popBackStack();
+ } else {
+ Log.i(LOGTAG, "onNavigateUp(): finishing activity");
+ finish();
+ }
+ return true;
+ }
+
+ // Overriding this method is necessary because before Oreo the PreferenceActivity didn't
+ // correctly handle the Home button (Up button). This was implemented in Oreo (Android 8+,
+ // API 26+).
+ // https://android.googlesource.com/platform/frameworks/base/+/6af15ebcfec64d0…
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item != null && item.getItemId() == android.R.id.home) {
+ Log.i(LOGTAG, "onOptionsItemSelected(): Home");
+ onNavigateUp();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ // Helper abstract Fragment with common methods
+ public static abstract class TorNetworkPreferenceFragment extends PreferenceFragment {
+ protected TorPreferences mTorPrefAct;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // This is only ever a TorPreferences
+ mTorPrefAct = (TorPreferences) getActivity();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mTorPrefAct.setFragment(this);
+ }
+
+ // Implement this callback in child Fragments
+ public void onSaveState() {
+ }
+
+ // Helper method for walking a View hierarchy and printing the children
+ protected void walkViewTree(View view, int depth) {
+ if (view instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) view;
+ int childIdx = 0;
+ for (; childIdx < vg.getChildCount(); childIdx++) {
+ walkViewTree(vg.getChildAt(childIdx), depth + 1);
+ }
+ }
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view: " + view);
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view id: " + view.getId());
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view is focused: " + view.isFocused());
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view is enabled: " + view.isEnabled());
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view is selected: " + view.isSelected());
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view is in touch mode: " + view.isInTouchMode());
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view is activated: " + view.isActivated());
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view is clickable: " + view.isClickable());
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view is focusable: " + view.isFocusable());
+ Log.i(LOGTAG, "walkViewTree: " + depth + ": view is FocusableInTouchMode: " + view.isFocusableInTouchMode());
+ }
+
+ // Helper returning the ListView
+ protected ListView getListView(View view) {
+ if (!(view instanceof ViewGroup) || view == null) {
+ return null;
+ }
+
+ View rawListView = view.findViewById(android.R.id.list);
+ if (!(rawListView instanceof ListView) || rawListView == null) {
+ return null;
+ }
+
+ return (ListView) rawListView;
+ }
+
+ // Get Bridges associated with the provided pref key saved in the
+ // provided SharedPreferences. Return null if the SharedPreferences
+ // is null or if there isn't any value associated with the pref.
+ protected String getBridges(SharedPreferences sharedPrefs, String pref) {
+ if (sharedPrefs == null) {
+ Log.w(LOGTAG, "getBridges: sharedPrefs is null");
+ return null;
+ }
+ return sharedPrefs.getString(pref, null);
+ }
+
+ // Save the bridge type and bridge line preferences.
+ //
+ // Save the bridgesType with the PREFS_BRIDGES_TYPE pref as the key
+ // (for future lookup). If bridgesType is null, then save the
+ // bridgesLines with the PREFS_BRIDGES_PROVIDE pref as the key, and
+ // use tor-android-service's helper method and enable
+ // tor-android-service's bridge pref.
+ protected boolean setBridges(SharedPreferences.Editor editor, String bridgesType, String bridgesLines) {
+ if (editor == null) {
+ Log.w(LOGTAG, "setBridges: editor is null");
+ return false;
+ }
+ Log.i(LOGTAG, "Saving bridge type preference: " + bridgesType);
+ Log.i(LOGTAG, "Saving bridge line preference: " + bridgesLines);
+
+ // If bridgesType is null, then clear the pref and save the bridgesLines
+ // as a provided bridge. If bridgesType is not null, then save the type
+ // but don't save it as a provided bridge.
+ editor.putString(PREFS_BRIDGES_TYPE, bridgesType);
+ if (bridgesType == null) {
+ editor.putString(PREFS_BRIDGES_PROVIDE, bridgesLines);
+ } else {
+ editor.putString(PREFS_BRIDGES_PROVIDE, null);
+ }
+
+ if (!editor.commit()) {
+ return false;
+ }
+
+ // Set tor-android service's preference
+ Prefs.setBridgesList(bridgesLines);
+
+ // If either of these are not null, then we're enabling bridges
+ boolean bridgesAreEnabled = (bridgesType != null) || (bridgesLines != null);
+ // Inform tor-android-service bridges are enabled
+ Prefs.putBridgesEnabled(bridgesAreEnabled);
+ return true;
+ }
+
+ // Disable the bridges.enabled Preference
+ protected void disableBridges(PreferenceFragment frag) {
+ if (frag == null) {
+ Log.w(LOGTAG, "disableBridges: frag is null");
+ return;
+ }
+
+ SwitchPreference bridgesEnabled = (SwitchPreference) frag.findPreference(PREFS_BRIDGES_ENABLED);
+ Preference bridgesType = frag.findPreference(PREFS_BRIDGES_TYPE);
+ Preference bridgesProvide = frag.findPreference(PREFS_BRIDGES_PROVIDE);
+ Preference pref = null;
+
+ if (bridgesEnabled != null) {
+ Log.i(LOGTAG, "disableBridges: bridgesEnabled is not null");
+ pref = bridgesEnabled;
+ } else if (bridgesType != null) {
+ Log.i(LOGTAG, "disableBridges: bridgesType is not null");
+ pref = bridgesType;
+ } else if (bridgesProvide != null) {
+ Log.i(LOGTAG, "disableBridges: bridgesProvide is not null");
+ pref = bridgesProvide;
+ } else {
+ Log.w(LOGTAG, "disableBridges: all of the expected preferences are null?");
+ return;
+ }
+
+ // Clear the saved prefs (it's okay we're using a different
+ // SharedPreference.Editor here, they modify the same backend).
+ // In addition, passing null is equivalent to clearing the
+ // preference.
+ setBridges(pref.getEditor(), null, null);
+
+ if (bridgesEnabled != null) {
+ bridgesEnabled.setChecked(false);
+ }
+ }
+
+ // Set the current title
+ protected void setTitle(int resId) {
+ ActionBar actionBar = mTorPrefAct.getSupportActionBar();
+
+ if (actionBar == null) {
+ Log.w(LOGTAG, "setTitle: actionBar is null");
+ return;
+ }
+
+ actionBar.setTitle(resId);
+ }
+ }
+
+ // Fragment implementing the screen for enabling Bridges
+ public static class TorNetworkBridgesEnabledPreference extends TorNetworkPreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.preferences_tor_network_main);
+ }
+
+ // This class is instantiated within the OnClickListener of the
+ // PreferenceSwitch's Switch widget
+ public class BridgesEnabledSwitchOnClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ Log.i(LOGTAG, "bridgesEnabledSwitch clicked");
+ if (!(v instanceof Switch)) {
+ Log.w(LOGTAG, "View isn't an instance of Switch?");
+ return;
+ }
+
+ Switch bridgesEnabledSwitch = (Switch) v;
+
+ // The widget was pressed, now find the preference and set it
+ // such that it is synchronized with the widget.
+ final SwitchPreference bridgesEnabled = (SwitchPreference) TorNetworkBridgesEnabledPreference.this.findPreference(PREFS_BRIDGES_ENABLED);
+ if (bridgesEnabled == null) {
+ Log.w(LOGTAG, "onClick: bridgesEnabled is null?");
+ return;
+ }
+
+ bridgesEnabled.setChecked(bridgesEnabledSwitch.isChecked());
+
+ // Only launch the Fragment if we're enabling bridges.
+ if (bridgesEnabledSwitch.isChecked()) {
+ TorNetworkBridgesEnabledPreference.this.mTorPrefAct.startPreferenceFragment(new TorNetworkBridgeSelectPreference(), true);
+ } else {
+ disableBridges(TorNetworkBridgesEnabledPreference.this);
+ }
+ }
+ }
+
+ // This method must be overridden because, when creating Preferences, the
+ // creation of the View hierarchy occurs asynchronously. Usually
+ // onCreateView() gives us the View hierarchy as it is defined in the XML layout.
+ // However, with Preferences the layout is created across multiple threads and it
+ // usually isn't available at the time onCreateView() or onViewCreated() are
+ // called. As a result, we find the ListView (which is almost guaranteed to exist
+ // at this time) and we add an OnHierarchyChangeListener where we wait until the
+ // children are added into the tree.
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ final SwitchPreference bridgesEnabled = (SwitchPreference) findPreference(PREFS_BRIDGES_ENABLED);
+ if (bridgesEnabled == null) {
+ Log.w(LOGTAG, "onViewCreated: bridgesEnabled is null?");
+ return;
+ }
+
+ // If we return from either of the "Select Bridge Type" screen
+ // or "Provide Bridge" screen without selecting or inputing
+ // any value, then we could arrive here without any bridge
+ // saved/enabled but this switch is enabled. Disable it.
+ if (!Prefs.bridgesEnabled()) {
+ bridgesEnabled.setChecked(false);
+ }
+
+ // Decide if the configured bridges were provided by the user or
+ // selected from the list of bridge types
+ if (isBridgeProvided(bridgesEnabled)) {
+ String newSummary = getString(R.string.pref_tor_network_bridges_enabled_change_custom);
+ setBridgesEnabledSummaryAndOnClickListener(bridgesEnabled, newSummary, true);
+ } else if (Prefs.bridgesEnabled()) {
+ // If isBridgeProvided() returned false, but Prefs.bridgesEnabled() returns true.
+ // This means we have bridges, but they weren't provided by the user - therefore
+ // they must be built-in bridges.
+ String newSummary = getString(R.string.pref_tor_network_bridges_enabled_change_builtin);
+ setBridgesEnabledSummaryAndOnClickListener(bridgesEnabled, newSummary, false);
+ }
+
+ ListView lv = getListView(view);
+ if (lv == null) {
+ Log.i(LOGTAG, "onViewCreated: ListView not found");
+ return;
+ }
+
+ lv.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ Log.i(LOGTAG, "onChildViewAdded: Adding ListView child view");
+
+ setTitle(R.string.pref_tor_network_title);
+
+ // Make sure the Switch widget is synchronized with the preference
+ final Switch bridgesEnabledSwitch =
+ (Switch) parent.findViewById(android.R.id.switch_widget);
+
+ if (bridgesEnabledSwitch != null) {
+ bridgesEnabledSwitch.setChecked(bridgesEnabled.isChecked());
+
+ // When the Switch is pressed by the user, either load the next
+ // fragment (where the user chooses a bridge type), or return to
+ // the main bootstrapping screen.
+ bridgesEnabledSwitch.setOnClickListener(new BridgesEnabledSwitchOnClickListener());
+ }
+
+ final TextView bridgesEnabledSummary =
+ (TextView) parent.findViewById(android.R.id.summary);
+ if (bridgesEnabledSummary == null) {
+ Log.w(LOGTAG, "Bridge Enabled Summary is null, we can't enable the span");
+ return;
+ }
+
+ // Make the ClickableSpan clickable within the TextView.
+ // This is a requirement for using a ClickableSpan in
+ // setBridgesEnabledSummaryAndOnClickListener().
+ bridgesEnabledSummary.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ }
+ });
+ }
+
+ // This is a common OnClickListener for when the user clicks on the Change link.
+ // The span won't be clickable until the MovementMethod is set. This happens in
+ // onViewCreated within the OnHierarchyChangeListener we set on the ListView.
+ private void setBridgesEnabledSummaryAndOnClickListener(SwitchPreference bridgesEnabled, final String newSummary, final boolean custom) {
+ Log.i(LOGTAG, "Bridge Summary clicked");
+ if (bridgesEnabled == null) {
+ Log.w(LOGTAG, "Bridge Enabled switch is null");
+ return;
+ }
+
+ // Here we obtain the correct text, based on whether the bridges
+ // were provided (custom) or built-in. Using that text, we create
+ // a spannable string and find the substring "Change" within it.
+ // If it exists, we make that substring clickable.
+ // Note: TODO This breaks with localization.
+ if (newSummary == null) {
+ Log.w(LOGTAG, "R.string.pref_tor_network_bridges_enabled_change_builtin is null");
+ return;
+ }
+ int changeStart = newSummary.indexOf("Change");
+ if (changeStart == -1) {
+ Log.w(LOGTAG, "R.string.pref_tor_network_bridges_enabled_change_builtin doesn't contain 'Change'");
+ return;
+ }
+ SpannableString newSpannableSummary = new SpannableString(newSummary);
+ newSpannableSummary.setSpan(new ClickableSpan() {
+ @Override
+ public void onClick(View v) {
+ // If a custom (provided) bridge is configured, then
+ // open the BridgesProvide preference fragment. Else,
+ // open the built-in/bridge-type fragment.
+ Log.i(LOGTAG, "Span onClick!");
+
+ // Add this Fragment regardless of which Fragment we're showing next. If the Change
+ // link goes to the built-in bridges, then this is what we show the user. If the Change
+ // link goes to the provided bridges, then we consider this a deep-link and we inject the
+ // built-in bridges screen into the backstack so they are shown it when they press Back
+ // from the provided-bridges screen.
+ mTorPrefAct.startPreferenceFragment(new
+ TorNetworkBridgeSelectPreference(), true);
+
+ if (custom) {
+ mTorPrefAct.startPreferenceFragment(new
+ TorNetworkBridgeProvidePreference(), true);
+ }
+ }
+ },
+ // Begin the span
+ changeStart,
+ // End the span
+ newSummary.length(),
+ // Don't include new characters added into the spanned substring
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ bridgesEnabled.setSummaryOn(newSpannableSummary);
+ }
+
+ // We follow this logic:
+ // If the bridgesEnabled switch is off, then false
+ // If tor-android-service doesn't have bridges enabled, then false
+ // If PREFS_BRIDGES_PROVIDE is not null, then true
+ // Else false
+ private boolean isBridgeProvided(SwitchPreference bridgesEnabled) {
+ if (bridgesEnabled == null) {
+ Log.i(LOGTAG, "isBridgeProvided: bridgesEnabled is null");
+ return false;
+ }
+
+ if (!bridgesEnabled.isChecked()) {
+ Log.i(LOGTAG, "isBridgeProvided: bridgesEnabled is not checked");
+ return false;
+ }
+
+ if (!Prefs.bridgesEnabled()) {
+ Log.i(LOGTAG, "isBridgeProvided: bridges are not enabled");
+ return false;
+ }
+ SharedPreferences sharedPrefs = bridgesEnabled.getSharedPreferences();
+ boolean hasBridgeProvide =
+ sharedPrefs.getString(PREFS_BRIDGES_PROVIDE, null) != null;
+
+ Log.i(LOGTAG, "isBridgeProvided: We have provided bridges: " + hasBridgeProvide);
+ return hasBridgeProvide;
+ }
+ }
+
+ // Fragment implementing the screen for selecting a built-in Bridge type
+ public static class TorNetworkBridgeSelectPreference extends TorNetworkPreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.preferences_tor_network_select_bridge_type);
+ }
+
+ // Add OnClickListeners after the View is created
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ ListView lv = getListView(view);
+ if (lv == null) {
+ Log.i(LOGTAG, "onViewCreated: ListView not found");
+ return;
+ }
+
+ // Configure onClick handler for "Provide a Bridge" button
+ lv.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ setTitle(R.string.pref_tor_select_a_bridge_title);
+
+ // Set the previously chosen RadioButton as checked
+ final RadioGroup group = getBridgeTypeRadioGroup();
+ if (group == null) {
+ Log.w(LOGTAG, "Radio Group is null");
+ return;
+ }
+
+ final View titleAndSummaryView = parent.findViewById(R.id.title_and_summary);
+ if (titleAndSummaryView == null) {
+ Log.w(LOGTAG, "title and summary view is null");
+ group.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ titleAndSummaryView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ group.setVisibility(View.VISIBLE);
+ }
+ });
+
+ final View provideABridge = parent.findViewById(R.id.tor_network_provide_a_bridge);
+ if (provideABridge == null) {
+ return;
+ }
+
+ provideABridge.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Log.i(LOGTAG, "bridgesProvide clicked");
+ saveCurrentCheckedRadioButton();
+
+ mTorPrefAct.startPreferenceFragment(new TorNetworkBridgeProvidePreference(), true);
+ }
+ });
+
+ final TextView provideABridgeSummary = (TextView) parent.findViewById(R.id.tor_network_provide_a_bridge_summary);
+ if (provideABridgeSummary == null) {
+ Log.i(LOGTAG, "provideABridgeSummary is null");
+ return;
+ }
+
+ Preference bridgesTypePref = findPreference(PREFS_BRIDGES_TYPE);
+ if (bridgesTypePref == null) {
+ return;
+ }
+
+ SharedPreferences sharedPrefs = bridgesTypePref.getSharedPreferences();
+ String provideBridges = sharedPrefs.getString(PREFS_BRIDGES_PROVIDE, null);
+ if (provideBridges != null) {
+ if (provideBridges.indexOf("\n") != -1) {
+ provideABridgeSummary.setText(R.string.pref_tor_network_using_multiple_provided_bridges);
+ } else {
+ String summary = getString(R.string.pref_tor_network_using_a_provided_bridge, provideBridges);
+ provideABridgeSummary.setText(summary);
+ }
+ }
+
+ final String configuredBridgeType = getBridges(bridgesTypePref.getSharedPreferences(), PREFS_BRIDGES_TYPE);
+ if (configuredBridgeType == null) {
+ return;
+ }
+
+ int buttonId = -1;
+ // Note: Keep these synchronized with the layout xml file.
+ switch (configuredBridgeType) {
+ case "obfs4":
+ buttonId = R.id.radio_pref_bridges_obfs4;
+ break;
+ case "meek":
+ buttonId = R.id.radio_pref_bridges_meek_azure;
+ break;
+ case "obfs3":
+ buttonId = R.id.radio_pref_bridges_obfs3;
+ break;
+ }
+
+ if (buttonId != -1) {
+ group.check(buttonId);
+ // If a bridge is selected, then make the list visible
+ group.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ }
+ });
+
+ }
+
+ // Save the checked RadioButton in the SharedPreferences
+ private boolean saveCurrentCheckedRadioButton() {
+ ListView lv = getListView(getView());
+ if (lv == null) {
+ Log.w(LOGTAG, "ListView is null");
+ return false;
+ }
+
+ RadioGroup group = getBridgeTypeRadioGroup();
+ if (group == null) {
+ Log.w(LOGTAG, "RadioGroup is null");
+ return false;
+ }
+
+ int checkedId = group.getCheckedRadioButtonId();
+ RadioButton selectedBridgeType = lv.findViewById(checkedId);
+ if (selectedBridgeType == null) {
+ Log.w(LOGTAG, "RadioButton is null");
+ return false;
+ }
+
+ String bridgesType = selectedBridgeType.getText().toString();
+ if (bridgesType == null) {
+ // We don't know with which bridgesType this Id is associated
+ Log.w(LOGTAG, "RadioButton has null text");
+ return false;
+ }
+
+ // Currently obfs4 is the recommended pluggable transport. As a result,
+ // the text contains " (recommended)". This won't be expected elsewhere,
+ // so replace the string with only the pluggable transport name.
+ // This will need updating when another transport is "recommended".
+ //
+ // Similarly, if meek-azure is chosen, substitute it with "meek"
+ // (tor-android-service only handles these keywords specially if
+ // they are less than 5 characters).
+ if (bridgesType.contains("obfs4")) {
+ bridgesType = "obfs4";
+ } else if (bridgesType.contains("meek-azure")) {
+ bridgesType = "meek";
+ }
+
+ Preference bridgesTypePref = findPreference(PREFS_BRIDGES_TYPE);
+ if (bridgesTypePref == null) {
+ Log.w(LOGTAG, PREFS_BRIDGES_TYPE + " preference not found");
+ disableBridges(this);
+ return false;
+ }
+
+ if (!setBridges(bridgesTypePref.getEditor(), bridgesType, bridgesType)) {
+ Log.w(LOGTAG, "Saving Bridge preference failed.");
+ disableBridges(this);
+ return false;
+ }
+
+ return true;
+ }
+
+ // Handle onSaveState when the user presses Back. Save the selected
+ // built-in bridge type.
+ @Override
+ public void onSaveState() {
+ saveCurrentCheckedRadioButton();
+ }
+
+ // Find the RadioGroup within the View hierarchy now.
+ private RadioGroup getBridgeTypeRadioGroup() {
+ ListView lv = getListView(getView());
+ if (lv == null) {
+ Log.w(LOGTAG, "ListView is null");
+ return null;
+ }
+ ViewParent listViewParent = lv.getParent();
+ // If the parent of this ListView isn't a View, then
+ // the RadioGroup doesn't exist
+ if (!(listViewParent instanceof View)) {
+ Log.w(LOGTAG, "ListView's parent isn't a View. Failing");
+ return null;
+ }
+ View lvParent = (View) listViewParent;
+ // Find the RadioGroup with this View hierarchy.
+ return (RadioGroup) lvParent.findViewById(R.id.pref_radio_group_builtin_bridges_type);
+ }
+ }
+
+ // Fragment implementing the screen for providing a Bridge
+ public static class TorNetworkBridgeProvidePreference extends TorNetworkPreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.preferences_tor_network_provide_bridge);
+ }
+
+ // If there is a provided bridge saved in the preference,
+ // then fill-in the text field with that value.
+ private void setBridgeProvideText(View parent) {
+ final View provideBridge1 = parent.findViewById(R.id.tor_network_provide_bridge1);
+ final View provideBridge2 = parent.findViewById(R.id.tor_network_provide_bridge2);
+ final View provideBridge3 = parent.findViewById(R.id.tor_network_provide_bridge3);
+
+ EditText provideBridge1ET = null;
+ EditText provideBridge2ET = null;
+ EditText provideBridge3ET = null;
+
+ if (provideBridge1 != null) {
+ if (provideBridge1 instanceof EditText) {
+ provideBridge1ET = (EditText) provideBridge1;
+ }
+ }
+
+ if (provideBridge2 != null) {
+ if (provideBridge2 instanceof EditText) {
+ provideBridge2ET = (EditText) provideBridge2;
+ }
+ }
+
+ if (provideBridge3 != null) {
+ if (provideBridge3 instanceof EditText) {
+ provideBridge3ET = (EditText) provideBridge3;
+ }
+ }
+
+ Preference bridgesProvide = findPreference(PREFS_BRIDGES_PROVIDE);
+ if (bridgesProvide != null) {
+ Log.i(LOGTAG, "setBridgeProvideText: bridgesProvide isn't null");
+ String bridgesLines = getBridges(bridgesProvide.getSharedPreferences(), PREFS_BRIDGES_PROVIDE);
+ if (bridgesLines != null) {
+ Log.i(LOGTAG, "setBridgeProvideText: bridgesLines isn't null");
+ if (bridgesLines.contains("\n")) {
+ String[] lines = bridgesLines.split("\n");
+ if (provideBridge1ET != null && lines.length >= 1) {
+ provideBridge1ET.setText(lines[0]);
+ }
+ if (provideBridge2ET != null && lines.length >= 2) {
+ provideBridge2ET.setText(lines[1]);
+ }
+ if (provideBridge3ET != null && lines.length >= 3) {
+ provideBridge3ET.setText(lines[2]);
+ }
+ } else {
+ // Simply set the single line as the text field input if the text field exists.
+ if (provideBridge1ET != null) {
+ provideBridge1ET.setText(bridgesLines);
+ }
+ }
+ }
+ }
+ }
+
+ // See explanation of TorNetworkBridgesEnabledPreference.onViewCreated()
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ ListView lv = getListView(view);
+ if (lv == null) {
+ Log.i(LOGTAG, "onViewCreated: ListView not found");
+ return;
+ }
+ // The ListView is given "focus" by default when the EditText
+ // field is selected, this prevents typing anything into the field.
+ // We set FOCUS_AFTER_DESCENDANTS so the ListView's children are
+ // given focus (and, therefore, the EditText) before it is
+ // given focus.
+ lv.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+
+ // The preferences are adding into the ListView hierarchy asynchronously.
+ // We need the onChildViewAdded callback so we can modify the layout after
+ // the child is added.
+ lv.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ setTitle(R.string.pref_tor_provide_a_bridge_title);
+
+ // If we have a bridge line saved for this pref,
+ // then show the user
+ setBridgeProvideText(parent);
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ }
+ });
+ }
+
+ private String getBridgeLineFromView(View provideBridge) {
+ if (provideBridge != null) {
+ if (provideBridge instanceof EditText) {
+ Log.i(LOGTAG, "onSaveState: Saving bridge");
+ EditText provideBridgeET = (EditText) provideBridge;
+
+ // Get the bridge line (provided text) from the text
+ // field.
+ String bridgesLine = provideBridgeET.getText().toString();
+ if (bridgesLine != null && !bridgesLine.equals("")) {
+ return bridgesLine;
+ }
+ } else {
+ Log.w(LOGTAG, "onSaveState: provideBridge isn't an EditText");
+ }
+ }
+ return null;
+ }
+
+ // Save EditText field value when the Back button or Up button are pressed.
+ @Override
+ public void onSaveState() {
+ ListView lv = getListView(getView());
+ if (lv == null) {
+ Log.i(LOGTAG, "onSaveState: ListView not found");
+ return;
+ }
+
+ final View provideBridge1 = lv.findViewById(R.id.tor_network_provide_bridge1);
+ final View provideBridge2 = lv.findViewById(R.id.tor_network_provide_bridge2);
+ final View provideBridge3 = lv.findViewById(R.id.tor_network_provide_bridge3);
+
+ String bridgesLines = null;
+ String bridgesLine1 = getBridgeLineFromView(provideBridge1);
+ String bridgesLine2 = getBridgeLineFromView(provideBridge2);
+ String bridgesLine3 = getBridgeLineFromView(provideBridge3);
+
+ if (bridgesLine1 != null) {
+ Log.i(LOGTAG, "bridgesLine1 is not null.");
+ bridgesLines = bridgesLine1;
+ }
+
+ if (bridgesLine2 != null) {
+ Log.i(LOGTAG, "bridgesLine2 is not null.");
+ if (bridgesLines != null) {
+ // If bridgesLine1 was not null, then append a newline.
+ bridgesLines += "\n" + bridgesLine2;
+ } else {
+ bridgesLines = bridgesLine2;
+ }
+ }
+
+ if (bridgesLine3 != null) {
+ Log.i(LOGTAG, "bridgesLine3 is not null.");
+ if (bridgesLines != null) {
+ // If bridgesLine1 or bridgesLine2 were not null, then append a newline.
+ bridgesLines += "\n" + bridgesLine3;
+ } else {
+ bridgesLines = bridgesLine3;
+ }
+ }
+
+ Preference bridgesProvide = findPreference(PREFS_BRIDGES_PROVIDE);
+ if (bridgesProvide == null) {
+ Log.w(LOGTAG, PREFS_BRIDGES_PROVIDE + " preference not found");
+ disableBridges(this);
+ return;
+ }
+
+ if (bridgesLines == null) {
+ // If provided bridges are null/empty, then only disable all bridges if
+ // the user did not select a built-in bridge
+ String configuredBuiltinBridges = getBridges(bridgesProvide.getSharedPreferences(), PREFS_BRIDGES_TYPE);
+ if (configuredBuiltinBridges == null) {
+ Log.i(LOGTAG, "Custom bridges are empty. Disabling.");
+ disableBridges(this);
+ }
+ return;
+ }
+
+ // Set the preferences (both our preference and
+ // tor-android-service's preference)
+ Log.w(LOGTAG, "Saving Bridge preference: " + bridgesLines);
+ if (!setBridges(bridgesProvide.getEditor(), null, bridgesLines)) {
+ // TODO inform the user
+ Log.w(LOGTAG, "Saving Bridge preference failed.");
+ disableBridges(this);
+ }
+ }
+ }
+}
1
0