commit 7b5f5ee887cd6fd865e86784ad536b4c0136ce83 Author: Matthew Finkel Matthew.Finkel@gmail.com Date: Tue Apr 9 17:52:28 2019 +0000
fixup! Bug 28329 - Part 4. Add new Tor Bootstrapping and configuration screens --- .../app/src/main/res/layout/tor_bootstrap.xml | 9 +- .../base/java/org/mozilla/gecko/BrowserApp.java | 2 +- .../gecko/torbootstrap/TorBootstrapPanel.java | 203 +++++++++++++++++---- .../mozilla/gecko/torbootstrap/TorPreferences.java | 13 +- 4 files changed, 176 insertions(+), 51 deletions(-)
diff --git a/mobile/android/app/src/main/res/layout/tor_bootstrap.xml b/mobile/android/app/src/main/res/layout/tor_bootstrap.xml index ce2b1c910a44..af9c7d11d3f2 100644 --- a/mobile/android/app/src/main/res/layout/tor_bootstrap.xml +++ b/mobile/android/app/src/main/res/layout/tor_bootstrap.xml @@ -74,13 +74,10 @@ android:tint="#ffffffff" android:layout_height="wrap_content" android:layout_width="match_parent" - android:layout_marginTop="130dp" android:layout_marginBottom="37dp" - android:layout_marginRight="95dp" - android:layout_marginLeft="95dp" + 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" - android:paddingLeft="20dp" - android:paddingRight="20dp"/> + android:layout_above="@id/tor_bootstrap_last_status_message" /> </RelativeLayout> diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java index dce3dc1548c2..006d5c11f210 100644 --- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java @@ -3134,7 +3134,7 @@ public class BrowserApp extends GeckoApp // 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(false); + Tabs.getInstance().getSelectedTab().doReload(true);
// If we finished, then Tor bootstrapped 100% mTorNeedsStart = false; diff --git a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPanel.java b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPanel.java index 8d42b13a2a8e..2ee4c2528691 100644 --- a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPanel.java +++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorBootstrapPanel.java @@ -7,7 +7,6 @@ package org.mozilla.gecko.torbootstrap;
import android.app.Activity; import android.content.Intent; -import android.graphics.drawable.Animatable2; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; @@ -16,6 +15,7 @@ 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; @@ -44,6 +44,8 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg 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; @@ -85,6 +87,155 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg } }
+ // 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 = (int) (verticalPadding*4)/5; + bottomPadding = (int) 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 = (int) 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); @@ -121,6 +272,12 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg
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; }
@@ -193,6 +350,9 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg mOnionAlphaChangerRunning = false; } close(); + + // Remove the listener when we're done + mRoot.getViewTreeObserver().removeOnGlobalLayoutListener(mViewTreeLayoutListener); } }
@@ -281,9 +441,9 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg Log.i(LOGTAG, "mChangeOnionAlphaThread.getState(): is terminated"); mChangeOnionAlphaThread = null; } else { - // Don't null the reference in this case because then we'll start another - // background thread. We are currently in an unknown state, simply set - // the Running flag as false. + // 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) { @@ -302,7 +462,7 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg
// Synchronization across threads should not be necessary because there // shouldn't be any other threads relying on mOnionAlphaChangerRunning. - // We do this purely for safety. + // Do this purely for safety. synchronized(mOnionAlphaChangerLock) { mOnionAlphaChangerRunning = true; } @@ -317,7 +477,7 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg Log.w(LOGTAG, "startBootstrapping: mRoot is null?"); return; } - // We're starting bootstrap, transition into the bootstrapping-tor-panel + // 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?"); @@ -326,17 +486,7 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg
ImageView onionImg = (ImageView) mRoot.findViewById(R.id.tor_bootstrap_onion);
- // Replace the current non-animated image with the animation - onionImg.setImageResource(R.drawable.tor_spinning_onion); - Drawable drawableOnion = onionImg.getDrawable(); - if (Build.VERSION.SDK_INT >= 23 && drawableOnion instanceof Animatable2) { - Animatable2 spinningOnion = (Animatable2) drawableOnion; - // Begin spinning - spinningOnion.start(); - } else { - Log.i(LOGTAG, "Animatable2 is not supported (or bad inheritance), version: " + Build.VERSION.SDK_INT); - }
mOnionCurrentAlpha = 255; // The onion should have 100% alpha, begin decreasing it. @@ -388,27 +538,6 @@ public class TorBootstrapPanel extends FirstrunPanel implements TorBootstrapLogg
Drawable drawableOnion = onionImg.getDrawable();
- // If the connect button wasn't pressed previously, then this object is - // not an animation (it is most likely a BitmapDrawable). Only manipulate - // it when it is an Animatable2. - if (Build.VERSION.SDK_INT >= 23 && drawableOnion instanceof Animatable2) { - Animatable2 spinningOnion = (Animatable2) drawableOnion; - // spinningOnion is null if we didn't previously call startBootstrapping. - // If we reach here and spinningOnion is null, then there is likely a bug - // because stopBootstrapping() is called only when the user selects the - // gear button and we should only reach this block if the user pressed the - // connect button (thus creating and enabling the animation) and then - // pressing the gear button. Therefore, if the drawableOnion is an - // Animatable2, then spinningOnion should be non-null. - if (spinningOnion != null) { - spinningOnion.stop(); - - onionImg.setImageResource(R.drawable.tor_spinning_onion); - } - } else { - Log.i(LOGTAG, "Animatable2 is not supported (or bad inheritance), version: " + Build.VERSION.SDK_INT); - } - // Reset the onion's alpha value. onionImg.setImageAlpha(255);
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 9a8468292e7d..87ce1ec4bec6 100644 --- a/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java +++ b/mobile/android/base/java/org/mozilla/gecko/torbootstrap/TorPreferences.java @@ -342,7 +342,7 @@ public class TorPreferences extends AppCompatPreferenceActivity { Log.i(LOGTAG, "disableBridges: bridgesProvide is not null"); pref = bridgesProvide; } else { - Log.w(LOGTAG, "disableBridges: all the expected preferences are is null?"); + Log.w(LOGTAG, "disableBridges: all of the expected preferences are null?"); return; }
@@ -396,7 +396,7 @@ public class TorPreferences extends AppCompatPreferenceActivity { // such that it is synchronized with the widget. final SwitchPreference bridgesEnabled = (SwitchPreference) TorNetworkBridgesEnabledPreference.this.findPreference(PREFS_BRIDGES_ENABLED); if (bridgesEnabled == null) { - Log.w(LOGTAG, "onCreate: bridgesEnabled is null?"); + Log.w(LOGTAG, "onClick: bridgesEnabled is null?"); return; }
@@ -422,11 +422,10 @@ public class TorPreferences extends AppCompatPreferenceActivity { @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - setTitle(R.string.pref_tor_network_title);
final SwitchPreference bridgesEnabled = (SwitchPreference) findPreference(PREFS_BRIDGES_ENABLED); if (bridgesEnabled == null) { - Log.w(LOGTAG, "onCreate: bridgesEnabled is null?"); + Log.w(LOGTAG, "onViewCreated: bridgesEnabled is null?"); return; }
@@ -927,9 +926,9 @@ public class TorPreferences extends AppCompatPreferenceActivity { }
if (bridgesLine2 != null) { - // If bridgesLine1 was not null, then append a newline. 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; @@ -937,9 +936,9 @@ public class TorPreferences extends AppCompatPreferenceActivity { }
if (bridgesLine3 != null) { - // If bridgesLine1 was not null, then append a newline. 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; @@ -954,11 +953,11 @@ public class TorPreferences extends AppCompatPreferenceActivity { }
if (bridgesLines == null) { - Log.i(LOGTAG, "provideBridge is empty. Disabling."); // 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;
tbb-commits@lists.torproject.org