X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FViewSourceActivity.kt;h=e0a964ed203ebc35ea57f28cc241442301e2d209;hb=HEAD;hp=532eda553eb258e2c0809f8a14f762214b5a41b1;hpb=1d656c562831f535aa33903d44198dd890393f4f;p=PrivacyBrowserAndroid.git diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.kt deleted file mode 100644 index 532eda55..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.kt +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright © 2017-2021 Soren Stoutner . - * - * This file is part of Privacy Browser . - * - * Privacy Browser is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privacy Browser is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privacy Browser. If not, see . - */ - -package com.stoutner.privacybrowser.activities - -import android.content.res.Configuration -import android.os.Build -import android.os.Bundle -import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.style.ForegroundColorSpan -import android.util.TypedValue -import android.view.KeyEvent -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.view.View.OnFocusChangeListener -import android.view.WindowManager -import android.view.inputmethod.InputMethodManager -import android.widget.EditText -import android.widget.ProgressBar -import android.widget.TextView - -import androidx.appcompat.app.ActionBar -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar -import androidx.core.app.NavUtils -import androidx.fragment.app.DialogFragment -import androidx.lifecycle.ViewModelProvider -import androidx.preference.PreferenceManager -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout - -import com.google.android.material.snackbar.Snackbar - -import com.stoutner.privacybrowser.R -import com.stoutner.privacybrowser.dialogs.AboutViewSourceDialog -import com.stoutner.privacybrowser.dialogs.UntrustedSslCertificateDialog -import com.stoutner.privacybrowser.dialogs.UntrustedSslCertificateDialog.UntrustedSslCertificateListener -import com.stoutner.privacybrowser.helpers.ProxyHelper -import com.stoutner.privacybrowser.viewmodelfactories.WebViewSourceFactory -import com.stoutner.privacybrowser.viewmodels.WebViewSource - -import java.util.Locale - -// Declare the public constants. -const val CURRENT_URL = "current_url" -const val USER_AGENT = "user_agent" - -class ViewSourceActivity: AppCompatActivity(), UntrustedSslCertificateListener { - // Declare the class variables. - private lateinit var initialGrayColorSpan: ForegroundColorSpan - private lateinit var finalGrayColorSpan: ForegroundColorSpan - private lateinit var redColorSpan: ForegroundColorSpan - private lateinit var webViewSource: WebViewSource - - // Declare the class views. - private lateinit var urlEditText: EditText - private lateinit var requestHeadersTitleTextView: TextView - private lateinit var requestHeadersTextView: TextView - private lateinit var responseMessageTitleTextView: TextView - private lateinit var responseMessageTextView: TextView - private lateinit var responseHeadersTitleTextView: TextView - private lateinit var responseBodyTitleTextView: TextView - - override fun onCreate(savedInstanceState: Bundle?) { - // Get a handle for the shared preferences. - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext) - - // Get the preferences. - val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) - val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false) - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - - // Set the theme. - setTheme(R.style.PrivacyBrowser) - - // Run the default commands. - super.onCreate(savedInstanceState) - - // Get the launching intent - val intent = intent - - // Get the information from the intent. - val currentUrl = intent.getStringExtra(CURRENT_URL)!! - val userAgent = intent.getStringExtra(USER_AGENT)!! - - // Set the content view. - if (bottomAppBar) { - setContentView(R.layout.view_source_bottom_appbar) - } else { - setContentView(R.layout.view_source_top_appbar) - } - - // Get a handle for the toolbar. - val toolbar = findViewById(R.id.view_source_toolbar) - - // Set the support action bar. - setSupportActionBar(toolbar) - - // Get a handle for the action bar. - val actionBar = supportActionBar!! - - // Add the custom layout to the action bar. - actionBar.setCustomView(R.layout.view_source_app_bar) - - // Instruct the action bar to display a custom layout. - actionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM - - // Get handles for the views. - urlEditText = findViewById(R.id.url_edittext) - val progressBar = findViewById(R.id.progress_bar) - val swipeRefreshLayout = findViewById(R.id.view_source_swiperefreshlayout) - requestHeadersTitleTextView = findViewById(R.id.request_headers_title_textview) - requestHeadersTextView = findViewById(R.id.request_headers_textview) - responseMessageTitleTextView = findViewById(R.id.response_message_title_textview) - responseMessageTextView = findViewById(R.id.response_message_textview) - responseHeadersTitleTextView = findViewById(R.id.response_headers_title_textivew) - val responseHeadersTextView = findViewById(R.id.response_headers_textview) - responseBodyTitleTextView = findViewById(R.id.response_body_title_textview) - val responseBodyTextView = findViewById(R.id.response_body_textview) - - // Populate the URL text box. - urlEditText.setText(currentUrl) - - // Initialize the gray foreground color spans for highlighting the URLs. The deprecated `getColor()` must be used until the minimum API >= 23. - @Suppress("DEPRECATION") - initialGrayColorSpan = ForegroundColorSpan(resources.getColor(R.color.gray_500)) - @Suppress("DEPRECATION") - finalGrayColorSpan = ForegroundColorSpan(resources.getColor(R.color.gray_500)) - - // Get the current theme status. - val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK - - // Set the red color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. - redColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - @Suppress("DEPRECATION") - ForegroundColorSpan(resources.getColor(R.color.red_a700)) - } else { - @Suppress("DEPRECATION") - ForegroundColorSpan(resources.getColor(R.color.red_900)) - } - - // Apply text highlighting to the URL. - highlightUrlText() - - // Get a handle for the input method manager, which is used to hide the keyboard. - val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) - - // Remove the formatting from the URL when the user is editing the text. - urlEditText.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean -> - if (hasFocus) { // The user is editing the URL text box. - // Remove the highlighting. - urlEditText.text.removeSpan(redColorSpan) - urlEditText.text.removeSpan(initialGrayColorSpan) - urlEditText.text.removeSpan(finalGrayColorSpan) - } else { // The user has stopped editing the URL text box. - // Hide the soft keyboard. - inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0) - - // Move to the beginning of the string. - urlEditText.setSelection(0) - - // Reapply the highlighting. - highlightUrlText() - } - } - - // Set the refresh color scheme according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - swipeRefreshLayout.setColorSchemeResources(R.color.blue_700) - } else { - swipeRefreshLayout.setColorSchemeResources(R.color.violet_500) - } - - // Initialize a color background typed value. - val colorBackgroundTypedValue = TypedValue() - - // Get the color background from the theme. - theme.resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true) - - // Get the color background int from the typed value. - val colorBackgroundInt = colorBackgroundTypedValue.data - - // Set the swipe refresh background color. - swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt) - - // Populate the locale string. - val localeString = if (Build.VERSION.SDK_INT >= 24) { // SDK >= 24 has a list of locales. - // Get the list of locales. - val localeList = resources.configuration.locales - - // Initialize a string builder to extract the locales from the list. - val localesStringBuilder = StringBuilder() - - // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages. - var q = 10 - - // Populate the string builder with the contents of the locales list. - for (i in 0 until localeList.size()) { - // Append a comma if there is already an item in the string builder. - if (i > 0) { - localesStringBuilder.append(",") - } - - // Get the locale from the list. - val locale = localeList[i] - - // Add the locale to the string. `locale` by default displays as `en_US`, but WebView uses the `en-US` format. - localesStringBuilder.append(locale.language) - localesStringBuilder.append("-") - localesStringBuilder.append(locale.country) - - // If not the first locale, append `;q=0.x`, which drops by .1 for each removal from the main locale until q=0.1. - if (q < 10) { - localesStringBuilder.append(";q=0.") - localesStringBuilder.append(q) - } - - // Decrement `q` if it is greater than 1. - if (q > 1) { - q-- - } - - // Add a second entry for the language only portion of the locale. - localesStringBuilder.append(",") - localesStringBuilder.append(locale.language) - - // Append `1;q=0.x`, which drops by .1 for each removal form the main locale until q=0.1. - localesStringBuilder.append(";q=0.") - localesStringBuilder.append(q) - - // Decrement `q` if it is greater than 1. - if (q > 1) { - q-- - } - } - - // Store the populated string builder in the locale string. - localesStringBuilder.toString() - } else { // SDK < 24 only has a primary locale. - // Store the locale in the locale string. - Locale.getDefault().toString() - } - - // Instantiate the proxy helper. - val proxyHelper = ProxyHelper() - - // Get the current proxy. - val proxy = proxyHelper.getCurrentProxy(this) - - // Make the progress bar visible. - progressBar.visibility = View.VISIBLE - - // Set the progress bar to be indeterminate. - progressBar.isIndeterminate = true - - // Update the layout. - updateLayout(currentUrl) - - // Instantiate the WebView source factory. - val webViewSourceFactory: ViewModelProvider.Factory = WebViewSourceFactory(currentUrl, userAgent, localeString, proxy, contentResolver, MainWebViewActivity.executorService) - - // Instantiate the WebView source view model class. - webViewSource = ViewModelProvider(this, webViewSourceFactory).get(WebViewSource::class.java) - - // Create a source observer. - webViewSource.observeSource().observe(this, { sourceStringArray: Array -> - // Populate the text views. This can take a long time, and freezes the user interface, if the response body is particularly large. - requestHeadersTextView.text = sourceStringArray[0] - responseMessageTextView.text = sourceStringArray[1] - responseHeadersTextView.text = sourceStringArray[2] - responseBodyTextView.text = sourceStringArray[3] - - // Hide the progress bar. - progressBar.isIndeterminate = false - progressBar.visibility = View.GONE - - //Stop the swipe to refresh indicator if it is running - swipeRefreshLayout.isRefreshing = false - }) - - // Create an error observer. - webViewSource.observeErrors().observe(this, { errorString: String -> - // Display an error snackbar if the string is not `""`. - if (errorString != "") { - if (errorString.startsWith("javax.net.ssl.SSLHandshakeException")) { - // Instantiate the untrusted SSL certificate dialog. - val untrustedSslCertificateDialog = UntrustedSslCertificateDialog() - - // Show the untrusted SSL certificate dialog. - untrustedSslCertificateDialog.show(supportFragmentManager, getString(R.string.invalid_certificate)) - } else { - // Display a snackbar with the error message. - Snackbar.make(swipeRefreshLayout, errorString, Snackbar.LENGTH_LONG).show() - } - } - }) - - // Implement swipe to refresh. - swipeRefreshLayout.setOnRefreshListener { - // Make the progress bar visible. - progressBar.visibility = View.VISIBLE - - // Set the progress bar to be indeterminate. - progressBar.isIndeterminate = true - - // Get the URL. - val urlString = urlEditText.text.toString() - - // Update the layout. - updateLayout(urlString) - - // Get the updated source. - webViewSource.updateSource(urlString, false) - } - - // Set the go button on the keyboard to request new source data. - urlEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent -> - // Request new source data if the enter key was pressed. - if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { - // Hide the soft keyboard. - inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0) - - // Remove the focus from the URL box. - urlEditText.clearFocus() - - // Make the progress bar visible. - progressBar.visibility = View.VISIBLE - - // Set the progress bar to be indeterminate. - progressBar.isIndeterminate = true - - // Get the URL. - val urlString = urlEditText.text.toString() - - // Update the layout. - updateLayout(urlString) - - // Get the updated source. - webViewSource.updateSource(urlString, false) - - // Consume the key press. - return@setOnKeyListener true - } else { - // Do not consume the key press. - return@setOnKeyListener false - } - } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - // Inflate the menu. - menuInflater.inflate(R.menu.view_source_options_menu, menu) - - // Display the menu. - return true - } - - override fun onOptionsItemSelected(menuItem: MenuItem): Boolean { - // Instantiate the about dialog fragment. - val aboutDialogFragment: DialogFragment = AboutViewSourceDialog() - - // Show the about alert dialog. - aboutDialogFragment.show(supportFragmentManager, getString(R.string.about)) - - // Consume the event. - return true - } - - // This method must be named `goBack()` and must have a View argument to match the default back arrow in the app bar. - fun goBack(@Suppress("UNUSED_PARAMETER") view: View) { - // Go home. - NavUtils.navigateUpFromSameTask(this) - } - - override fun loadAnyway() { - // Load the URL anyway. - webViewSource.updateSource(urlEditText.text.toString(), true) - } - - private fun highlightUrlText() { - // Get a handle for the URL edit text. - val urlEditText = findViewById(R.id.url_edittext) - - // Get the URL string. - val urlString = urlEditText.text.toString() - - // Highlight the URL according to the protocol. - if (urlString.startsWith("file://")) { // This is a file URL. - // De-emphasize only the protocol. - urlEditText.text.setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE) - } else if (urlString.startsWith("content://")) { - // De-emphasize only the protocol. - urlEditText.text.setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE) - } else { // This is a web URL. - // Get the index of the `/` immediately after the domain name. - val endOfDomainName = urlString.indexOf("/", urlString.indexOf("//") + 2) - - // Get the base URL. - val baseUrl = if (endOfDomainName > 0) { // There is at least one character after the base URL. - // Get the base URL. - urlString.substring(0, endOfDomainName) - } else { // There are no characters after the base URL. - // Set the base URL to be the entire URL string. - urlString - } - - // Get the index of the last `.` in the domain. - val lastDotIndex = baseUrl.lastIndexOf(".") - - // Get the index of the penultimate `.` in the domain. - val 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.text.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.text.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.text.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.text.setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE) - } - } - - // De-emphasize the text after the domain name. - if (endOfDomainName > 0) { - urlEditText.text.setSpan(finalGrayColorSpan, endOfDomainName, urlString.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) - } - } - } - - private fun updateLayout(urlString: String) { - if (urlString.startsWith("content://")) { // This is a content URL. - // Hide the unused text views. - requestHeadersTitleTextView.visibility = View.GONE - requestHeadersTextView.visibility = View.GONE - responseMessageTitleTextView.visibility = View.GONE - responseMessageTextView.visibility = View.GONE - - // Change the text of the remaining title text views. - responseHeadersTitleTextView.setText(R.string.content_metadata) - responseBodyTitleTextView.setText(R.string.content_data) - } else { // This is not a content URL. - // Show the views. - requestHeadersTitleTextView.visibility = View.VISIBLE - requestHeadersTextView.visibility = View.VISIBLE - responseMessageTitleTextView.visibility = View.VISIBLE - responseMessageTextView.visibility = View.VISIBLE - - // Restore the text of the other title text views. - responseHeadersTitleTextView.setText(R.string.response_headers) - responseBodyTitleTextView.setText(R.string.response_body) - } - } -} \ No newline at end of file