From ac56c9d4b45d50bc161ec07c4d6b0760e6611206 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Wed, 19 Dec 2018 14:45:29 -0700 Subject: [PATCH] Add swipe to refresh to the View Source activity. https://redmine.stoutner.com/issues/243 --- .idea/dictionaries/soren.xml | 1 + app/src/free/res/layout/main_webview.xml | 2 +- app/src/main/assets/ru/guide_tor_dark.html | 8 +- app/src/main/assets/ru/guide_tor_light.html | 8 +- .../activities/MainWebViewActivity.java | 4 +- .../activities/ViewSourceActivity.java | 148 ++++++++++++----- app/src/main/res/layout/main_webview.xml | 2 +- .../layout/view_source_coordinatorlayout.xml | 152 +++++++++--------- 8 files changed, 203 insertions(+), 122 deletions(-) diff --git a/.idea/dictionaries/soren.xml b/.idea/dictionaries/soren.xml index 5c9a66a3..adbe0dc0 100644 --- a/.idea/dictionaries/soren.xml +++ b/.idea/dictionaries/soren.xml @@ -147,6 +147,7 @@ subfolders sublists sufficientlysecure + swiperefreshlayout swipetorefresh tablayout techrepublic diff --git a/app/src/free/res/layout/main_webview.xml b/app/src/free/res/layout/main_webview.xml index 49c7ed83..48e12f03 100644 --- a/app/src/free/res/layout/main_webview.xml +++ b/app/src/free/res/layout/main_webview.xml @@ -43,7 +43,7 @@ diff --git a/app/src/main/assets/ru/guide_tor_dark.html b/app/src/main/assets/ru/guide_tor_dark.html index b6e57597..1f73e9f0 100644 --- a/app/src/main/assets/ru/guide_tor_dark.html +++ b/app/src/main/assets/ru/guide_tor_dark.html @@ -77,10 +77,10 @@ -

Downloading Files Via Tor

-

When Orbot is operating in proxy mode, browsing the internet using Privacy Browser will be router through the Tor network, but file downloads will not. - This is because Privacy Browser uses Android’s builtin download manager to download files, which doesn't have a proxy option. - Users who want to download files via Orbot need to enable its VPN mode.

+

Загрузка файлов через сеть Tor

+

При работе Orbot в режиме проксирования, весь трафик Privacy Browser будет маршрутизироваться через сеть Tor за исключением загружаемых файлов. + Это связано с тем, что Privacy Browser использует встроенный менеджер загрузок Android который не имеет возможности проксирования. + Пользователи, которые хотят загружать файлы через Orbot, должны включить режим VPN.

diff --git a/app/src/main/assets/ru/guide_tor_light.html b/app/src/main/assets/ru/guide_tor_light.html index 4ba7afb4..037c970f 100644 --- a/app/src/main/assets/ru/guide_tor_light.html +++ b/app/src/main/assets/ru/guide_tor_light.html @@ -77,10 +77,10 @@ -

Downloading Files Via Tor

-

When Orbot is operating in proxy mode, browsing the internet using Privacy Browser will be router through the Tor network, but file downloads will not. - This is because Privacy Browser uses Android’s builtin download manager to download files, which doesn't have a proxy option. - Users who want to download files via Orbot need to enable its VPN mode.

+

Загрузка файлов через сеть Tor

+

При работе Orbot в режиме проксирования, весь трафик Privacy Browser будет маршрутизироваться через сеть Tor за исключением загружаемых файлов. + Это связано с тем, что Privacy Browser использует встроенный менеджер загрузок Android который не имеет возможности проксирования. + Пользователи, которые хотят загружать файлы через Orbot, должны включить режим VPN.

diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index d4ea56be..ec3a24dd 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -816,7 +816,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook }); // Implement swipe to refresh. - swipeRefreshLayout = findViewById(R.id.swipe_refreshlayout); + swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload()); // Set the swipe to refresh color according to the theme. @@ -929,7 +929,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Hide the keyboard (if displayed). inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); - // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menues, which otherwise draw above the open drawers. + // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers. urlTextBox.clearFocus(); mainWebView.clearFocus(); } diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java index 34f8f3bd..67898509 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java @@ -19,7 +19,6 @@ package com.stoutner.privacybrowser.activities; -import android.annotation.SuppressLint; import android.app.Activity; import android.app.DialogFragment; import android.content.Context; @@ -31,6 +30,7 @@ import android.os.Bundle; import android.os.LocaleList; import android.preference.PreferenceManager; import android.support.v4.app.NavUtils; +import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; @@ -56,6 +56,7 @@ import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; import java.net.HttpURLConnection; import java.net.URL; import java.util.Locale; @@ -126,7 +127,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Get a handle for the input method manager, which is used to hide the keyboard. InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - // Let Android Studio know that we aren't worried about the input method manager being null. + // Remove the lint warning that the input method manager might be null. assert inputMethodManager != null; // Remove the formatting from the URL when the user is editing the text. @@ -158,7 +159,7 @@ public class ViewSourceActivity extends AppCompatActivity { urlEditText.clearFocus(); // Get new source data for the current URL. - new GetSource().execute(urlEditText.getText().toString()); + new GetSource(this).execute(urlEditText.getText().toString()); // Consume the key press. return true; @@ -168,8 +169,20 @@ public class ViewSourceActivity extends AppCompatActivity { } }); - // Get the source as an `AsyncTask`. - new GetSource().execute(formattedUrlString); + // Implement swipe to refresh. + SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.view_source_swiperefreshlayout); + swipeRefreshLayout.setOnRefreshListener(() -> new GetSource(this).execute(urlEditText.getText().toString())); + + // Set the swipe to refresh color according to the theme. + if (MainWebViewActivity.darkTheme) { + swipeRefreshLayout.setColorSchemeResources(R.color.blue_600); + swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_800); + } else { + swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); + } + + // Get the source using an AsyncTask. + new GetSource(this).execute(formattedUrlString); } @Override @@ -202,39 +215,77 @@ public class ViewSourceActivity extends AppCompatActivity { // Get a handle for the URL EditText. EditText urlEditText = findViewById(R.id.url_edittext); - // Get the URL. + // Get the URL string. String urlString = urlEditText.getText().toString(); - // Highlight the beginning of the URL. + // Get the index of the `/` immediately after the domain name. + int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); + + // Create a base URL string. + String baseUrl; + + // Get the base URL. + if (endOfDomainName > 0) { // There is at least one character after the base URL. + // Get the base URL. + baseUrl = urlString.substring(0, endOfDomainName); + } else { // There are no characters after the base URL. + // Set the base URL to be the entire URL string. + baseUrl = urlString; + } + + // Get the index of the last `.` in the domain. + int lastDotIndex = baseUrl.lastIndexOf("."); + + // Get the index of the penultimate `.` in the domain. + int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1); + + // Markup the beginning of the URL. if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // De-emphasize subdomains. + if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. + urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. + // De-emphasize the protocol and the additional subdomains. + urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // There is only one subdomain in the domain name. + // De-emphasize only the protocol. + urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } } - // Get the index of the `/` immediately after the domain name. - int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); - // De-emphasize the text after the domain name. if (endOfDomainName > 0) { urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); } } - // The first `String` declares the parameters. The `Void` does not declare progress units. The last `String` contains the results. - // `StaticFieldLeaks` are suppressed so that Android Studio doesn't complain about running an AsyncTask in a non-static context. - @SuppressLint("StaticFieldLeak") - private class GetSource extends AsyncTask { - // The class variables pass information from `doInBackground()` to `onPostExecute()`. - SpannableStringBuilder responseMessageBuilder; - SpannableStringBuilder requestHeadersBuilder; - SpannableStringBuilder responseHeadersBuilder; + // `String` declares the parameters. `Void` does not declare progress units. `String[]` contains the results. + private static class GetSource extends AsyncTask { + // Create a weak reference to the calling activity. + private WeakReference activityWeakReference; + + // Populate the weak reference to the calling activity. + GetSource(Activity activity) { + activityWeakReference = new WeakReference<>(activity); + } // `onPreExecute()` operates on the UI thread. @Override protected void onPreExecute() { + // Get a handle for the activity. + Activity viewSourceActivity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((viewSourceActivity == null) || (viewSourceActivity.isFinishing())) { + return; + } + // Get a handle for the progress bar. - ProgressBar progressBar = findViewById(R.id.progress_bar); + ProgressBar progressBar = viewSourceActivity.findViewById(R.id.progress_bar); // Make the progress bar visible. progressBar.setVisibility(View.VISIBLE); @@ -244,9 +295,20 @@ public class ViewSourceActivity extends AppCompatActivity { } @Override - protected String doInBackground(String... formattedUrlString) { - // Initialize the response body `String`. - String responseBodyString = ""; + protected SpannableStringBuilder[] doInBackground(String... formattedUrlString) { + // Initialize the response body String. + SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder(); + SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder(); + SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder(); + SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder(); + + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || (activity.isFinishing())) { + return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder}; + } // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`. try { @@ -347,7 +409,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); // Only populate `Do Not Track` if it is enabled. if (sharedPreferences.getBoolean("do_not_track", false)) { @@ -391,7 +453,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Populate the locale string. if (Build.VERSION.SDK_INT >= 24) { // SDK >= 24 has a list of locales. // Get the list of locales. - LocaleList localeList = getResources().getConfiguration().getLocales(); + LocaleList localeList = activity.getResources().getConfiguration().getLocales(); // Initialize a string builder to extract the locales from the list. StringBuilder localesStringBuilder = new StringBuilder(); @@ -557,7 +619,7 @@ public class ViewSourceActivity extends AppCompatActivity { inputStream.close(); // Populate the response body string with the contents of the byte array output stream. - responseBodyString = byteArrayOutputStream.toString(); + responseBodyBuilder.append(byteArrayOutputStream.toString()); } finally { // Disconnect `httpUrlConnection`. httpUrlConnection.disconnect(); @@ -567,28 +629,40 @@ public class ViewSourceActivity extends AppCompatActivity { } // Return the response body string as the result. - return responseBodyString; + return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder}; } // `onPostExecute()` operates on the UI thread. @Override - protected void onPostExecute(String responseBodyString){ + protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){ + // Get a handle the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || (activity.isFinishing())) { + return; + } + // Get handles for the text views. - TextView requestHeadersTextView = findViewById(R.id.request_headers); - TextView responseMessageTextView = findViewById(R.id.response_message); - TextView responseHeadersTextView = findViewById(R.id.response_headers); - TextView responseBodyTextView = findViewById(R.id.response_body); - ProgressBar progressBar = findViewById(R.id.progress_bar); + TextView requestHeadersTextView = activity.findViewById(R.id.request_headers); + TextView responseMessageTextView = activity.findViewById(R.id.response_message); + TextView responseHeadersTextView = activity.findViewById(R.id.response_headers); + TextView responseBodyTextView = activity.findViewById(R.id.response_body); + ProgressBar progressBar = activity.findViewById(R.id.progress_bar); + SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.view_source_swiperefreshlayout); // Populate the text views. - requestHeadersTextView.setText(requestHeadersBuilder); - responseMessageTextView.setText(responseMessageBuilder); - responseHeadersTextView.setText(responseHeadersBuilder); - responseBodyTextView.setText(responseBodyString); + requestHeadersTextView.setText(viewSourceStringArray[0]); + responseMessageTextView.setText(viewSourceStringArray[1]); + responseHeadersTextView.setText(viewSourceStringArray[2]); + responseBodyTextView.setText(viewSourceStringArray[3]); // Hide the progress bar. progressBar.setIndeterminate(false); progressBar.setVisibility(View.GONE); + + //Stop the swipe to refresh indicator if it is running + swipeRefreshLayout.setRefreshing(false); } } } \ No newline at end of file diff --git a/app/src/main/res/layout/main_webview.xml b/app/src/main/res/layout/main_webview.xml index 74ae827c..20ffbc66 100644 --- a/app/src/main/res/layout/main_webview.xml +++ b/app/src/main/res/layout/main_webview.xml @@ -38,7 +38,7 @@ android:visibility="gone" /> diff --git a/app/src/main/res/layout/view_source_coordinatorlayout.xml b/app/src/main/res/layout/view_source_coordinatorlayout.xml index 71a76676..5d8f191e 100644 --- a/app/src/main/res/layout/view_source_coordinatorlayout.xml +++ b/app/src/main/res/layout/view_source_coordinatorlayout.xml @@ -69,83 +69,89 @@ - + - - - - - - - - - - - - - - - - + android:layout_width="match_parent" > - - - - - - + android:orientation="vertical" + android:layout_margin="10dp" > + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.45.2