X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FViewSourceActivity.java;h=db9bf7974f6a52aa58d0256c2d407cc8d0aa6042;hp=34f8f3bd7b49ddff912075fd26a3337b0fe62488;hb=6a85cc5ca039054a24c4601de785e0bc1a234722;hpb=8ee786ff2f0c11ebb7abcd4d51e7013c5c5a5845 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..db9bf797 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. @@ -140,10 +141,11 @@ public class ViewSourceActivity extends AppCompatActivity { // Hide the soft keyboard. inputMethodManager.hideSoftInputFromWindow(urlEditText.getWindowToken(), 0); + // Move to the beginning of the string. + urlEditText.setSelection(0); + // Reapply the highlighting. highlightUrlText(); - - } }); @@ -157,8 +159,13 @@ public class ViewSourceActivity extends AppCompatActivity { // Remove the focus from the URL box. urlEditText.clearFocus(); - // Get new source data for the current URL. - new GetSource().execute(urlEditText.getText().toString()); + // Get the URL. + String url = urlEditText.getText().toString(); + + // Get new source data for the current URL if it beings with `http`. + if (url.startsWith("http")) { + new GetSource(this).execute(url); + } // Consume the key press. return true; @@ -168,8 +175,33 @@ 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(() -> { + // Get the URL. + String url = urlEditText.getText().toString(); + + // Get new source data for the URL if it begins with `http`/ + if (url.startsWith("http")) { + new GetSource(this).execute(url); + } else { + // Stop the refresh animation. + swipeRefreshLayout.setRefreshing(false); + } + }); + + // 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 if the URL begins with `http`. + if (formattedUrlString.startsWith("http")) { + new GetSource(this).execute(formattedUrlString); + } } @Override @@ -202,39 +234,86 @@ 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. - if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. - urlEditText.getText().setSpan(redColorSpan, 0, 7, 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); - } + // Highlight the URL according to the protocol. + if (urlString.startsWith("file://")) { // This is a file URL. + // De-emphasize only the protocol. + urlEditText.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else if (urlString.startsWith("content://")) { + // De-emphasize only the protocol. + urlEditText.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // This is a web 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 `/` immediately after the domain name. - int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); + // Get the index of the last `.` in the domain. + int lastDotIndex = baseUrl.lastIndexOf("."); - // De-emphasize the text after the domain name. - if (endOfDomainName > 0) { - urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + // 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. + 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); + } + } + + // 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 +323,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 +437,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 +481,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 +647,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 +657,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); - - // Populate the text views. - requestHeadersTextView.setText(requestHeadersBuilder); - responseMessageTextView.setText(responseMessageBuilder); - responseHeadersTextView.setText(responseHeadersBuilder); - responseBodyTextView.setText(responseBodyString); + 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. This can take a long time, and freeze the user interface, if the response body is particularly large. + 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