import android.view.View.OnFocusChangeListener
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
+import android.widget.Button
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.TextView
import com.google.android.material.snackbar.Snackbar
import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.dialogs.AVAILABLE_CIPHERS
+import com.stoutner.privacybrowser.dialogs.SSL_CERTIFICATE
import com.stoutner.privacybrowser.dialogs.AboutViewHeadersDialog
+import com.stoutner.privacybrowser.dialogs.ViewHeadersDetailDialog
import com.stoutner.privacybrowser.dialogs.UntrustedSslCertificateDialog
import com.stoutner.privacybrowser.dialogs.UntrustedSslCertificateDialog.UntrustedSslCertificateListener
import com.stoutner.privacybrowser.helpers.ProxyHelper
class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener {
// Declare the class variables.
+ private lateinit var appliedCipherString: String
+ private lateinit var availableCiphersString: String
private lateinit var headersViewModel: HeadersViewModel
private lateinit var initialGrayColorSpan: ForegroundColorSpan
private lateinit var finalGrayColorSpan: ForegroundColorSpan
private lateinit var redColorSpan: ForegroundColorSpan
+ private lateinit var sslCertificateString: String
// Declare the class views.
private lateinit var urlEditText: EditText
+ private lateinit var sslInformationTitleTextView: TextView
+ private lateinit var sslInformationTextView: TextView
+ private lateinit var ciphersButton: Button
+ private lateinit var certificateButton: Button
private lateinit var requestHeadersTitleTextView: TextView
private lateinit var requestHeadersTextView: TextView
private lateinit var responseMessageTitleTextView: TextView
urlEditText = findViewById(R.id.url_edittext)
val progressBar = findViewById<ProgressBar>(R.id.progress_bar)
val swipeRefreshLayout = findViewById<SwipeRefreshLayout>(R.id.swiperefreshlayout)
+ sslInformationTitleTextView = findViewById(R.id.ssl_information_title_textview)
+ sslInformationTextView = findViewById(R.id.ssl_information_textview)
+ ciphersButton = findViewById(R.id.ciphers_button)
+ certificateButton = findViewById(R.id.certificate_button)
requestHeadersTitleTextView = findViewById(R.id.request_headers_title_textview)
requestHeadersTextView = findViewById(R.id.request_headers_textview)
responseMessageTitleTextView = findViewById(R.id.response_message_title_textview)
updateLayout(currentUrl)
// Instantiate the view headers factory.
- val viewHeadersFactory: ViewModelProvider.Factory = ViewHeadersFactory(currentUrl, userAgent, localesStringBuilder.toString(), proxy, contentResolver, MainWebViewActivity.executorService)
+ val viewHeadersFactory: ViewModelProvider.Factory = ViewHeadersFactory(application, currentUrl, userAgent, localesStringBuilder.toString(), proxy, contentResolver, MainWebViewActivity.executorService)
// Instantiate the headers view model.
headersViewModel = ViewModelProvider(this, viewHeadersFactory)[HeadersViewModel::class.java]
// Create a headers observer.
headersViewModel.observeHeaders().observe(this) { headersStringArray: Array<SpannableStringBuilder> ->
// Populate the text views. This can take a long time, and freezes the user interface, if the response body is particularly large.
- requestHeadersTextView.text = headersStringArray[0]
- responseMessageTextView.text = headersStringArray[1]
- responseHeadersTextView.text = headersStringArray[2]
- responseBodyTextView.text = headersStringArray[3]
+ sslInformationTextView.text = headersStringArray[0]
+ requestHeadersTextView.text = headersStringArray[4]
+ responseMessageTextView.text = headersStringArray[5]
+ responseHeadersTextView.text = headersStringArray[6]
+ responseBodyTextView.text = headersStringArray[7]
+
+ // Populate the dialog strings.
+ appliedCipherString = headersStringArray[1].toString()
+ availableCiphersString = headersStringArray[2].toString()
+ sslCertificateString = headersStringArray[3].toString()
// Hide the progress bar.
progressBar.isIndeterminate = false
progressBar.visibility = View.GONE
- //Stop the swipe to refresh indicator if it is running
+ // Stop the swipe to refresh indicator if it is running
swipeRefreshLayout.isRefreshing = false
}
headersViewModel.updateHeaders(urlEditText.text.toString(), true)
}
+ // The view parameter cannot be removed because it is called from the layout onClick.
+ fun showCertificate(@Suppress("UNUSED_PARAMETER")view: View) {
+ // Instantiate an SSL certificate dialog.
+ val sslCertificateDialogFragment= ViewHeadersDetailDialog.displayDialog(SSL_CERTIFICATE, sslCertificateString)
+
+ // Show the dialog.
+ sslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate))
+ }
+
+ // The view parameter cannot be removed because it is called from the layout onClick.
+ fun showCiphers(@Suppress("UNUSED_PARAMETER")view: View) {
+ // Instantiate an SSL certificate dialog.
+ val ciphersDialogFragment= ViewHeadersDetailDialog.displayDialog(AVAILABLE_CIPHERS, availableCiphersString, appliedCipherString)
+
+ // Show the dialog.
+ ciphersDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate))
+ }
+
private fun updateLayout(urlString: String) {
if (urlString.startsWith("content://")) { // This is a content URL.
- // Hide the unused text views.
+ // Hide the unused views.
+ sslInformationTitleTextView.visibility = View.GONE
+ sslInformationTextView.visibility = View.GONE
+ ciphersButton.visibility = View.GONE
+ certificateButton.visibility = View.GONE
requestHeadersTitleTextView.visibility = View.GONE
requestHeadersTextView.visibility = View.GONE
responseMessageTitleTextView.visibility = View.GONE
responseHeadersTitleTextView.setText(R.string.content_metadata)
responseBodyTitleTextView.setText(R.string.content_data)
} else { // This is not a content URL.
- // Show the views.
+ // Set the status if the the SSL information views.
+ if (urlString.startsWith("http://")) { // This is an HTTP URL.
+ // Hide the SSL information views.
+ sslInformationTitleTextView.visibility = View.GONE
+ sslInformationTextView.visibility = View.GONE
+ ciphersButton.visibility = View.GONE
+ certificateButton.visibility = View.GONE
+ } else { // This is not an HTTP URL.
+ // Show the SSL information views.
+ sslInformationTitleTextView.visibility = View.VISIBLE
+ sslInformationTextView.visibility = View.VISIBLE
+ ciphersButton.visibility = View.VISIBLE
+ certificateButton.visibility = View.VISIBLE
+ }
+
+ // Show the other views.
requestHeadersTitleTextView.visibility = View.VISIBLE
requestHeadersTextView.visibility = View.VISIBLE
responseMessageTitleTextView.visibility = View.VISIBLE
package com.stoutner.privacybrowser.backgroundtasks
import android.annotation.SuppressLint
+import android.app.Application
import android.content.ContentResolver
import android.graphics.Typeface
import android.net.Uri
import android.text.style.StyleSpan
import android.webkit.CookieManager
+import com.stoutner.privacybrowser.R
import com.stoutner.privacybrowser.viewmodels.HeadersViewModel
import java.io.BufferedInputStream
class GetHeadersBackgroundTask {
- fun acquire(urlString: String, userAgent: String, localeString: String, proxy: Proxy, contentResolver: ContentResolver, headersViewModel: HeadersViewModel, ignoreSslErrors: Boolean):
+ fun acquire(application: Application, urlString: String, userAgent: String, localeString: String, proxy: Proxy, contentResolver: ContentResolver, headersViewModel: HeadersViewModel, ignoreSslErrors: Boolean):
Array<SpannableStringBuilder> {
// Initialize the spannable string builders.
+ val sslInformationBuilder = SpannableStringBuilder()
+ val appliedCipherBuilder = SpannableStringBuilder()
+ val availableCiphersBuilder = SpannableStringBuilder()
+ val sslCertificateBuilder = SpannableStringBuilder()
val requestHeadersBuilder = SpannableStringBuilder()
val responseMessageBuilder = SpannableStringBuilder()
val responseHeadersBuilder = SpannableStringBuilder()
val responseBodyBuilder = SpannableStringBuilder()
+ // Get the colon string.
+ val colonString = application.getString(R.string.colon)
+ val newLineString = System.getProperty("line.separator")
+
if (urlString.startsWith("content://")) { // This is a content URL.
// Attempt to read the content data. Return an error if this fails.
try {
for (i in 0 until contentCursor.columnCount) {
// Add a new line if this is not the first entry.
if (i > 0)
- responseHeadersBuilder.append(System.getProperty("line.separator"))
+ responseHeadersBuilder.append(newLineString)
// Add each header to the string builder.
responseHeadersBuilder.append(contentCursor.getColumnName(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- responseHeadersBuilder.append(": ")
+ responseHeadersBuilder.append(colonString)
responseHeadersBuilder.append(contentCursor.getString(i))
}
// Add the `Host` header to the string builder and format the text.
requestHeadersBuilder.append("Host", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
+ requestHeadersBuilder.append(colonString)
requestHeadersBuilder.append(url.host)
httpUrlConnection.setRequestProperty("Connection", "keep-alive")
// Add the `Connection` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Connection", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": keep-alive")
+ requestHeadersBuilder.append(colonString)
+ requestHeadersBuilder.append("keep-alive")
// Set the `Upgrade-Insecure-Requests` header property.
httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1")
// Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Upgrade-Insecure-Requests", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": 1")
+ requestHeadersBuilder.append(colonString)
+ requestHeadersBuilder.append("1")
// Set the `User-Agent` header property.
httpUrlConnection.setRequestProperty("User-Agent", userAgent)
// Add the `User-Agent` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("User-Agent", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
+ requestHeadersBuilder.append(colonString)
requestHeadersBuilder.append(userAgent)
httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none")
// Add the `Sec-Fetch-Site` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Sec-Fetch-Site", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": none")
+ requestHeadersBuilder.append(colonString)
+ requestHeadersBuilder.append("none")
// Set the `Sec-Fetch-Mode` header property.
httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate")
// Add the `Sec-Fetch-Mode` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Sec-Fetch-Mode", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": navigate")
+ requestHeadersBuilder.append(colonString)
+ requestHeadersBuilder.append("navigate")
// Set the `Sec-Fetch-User` header property.
httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1")
// Add the `Sec-Fetch-User` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Sec-Fetch-User", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ?1")
+ requestHeadersBuilder.append(colonString)
+ requestHeadersBuilder.append("?1")
// Set the `Accept` header property.
httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
// Add the `Accept` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Accept", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
+ requestHeadersBuilder.append(colonString)
requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
httpUrlConnection.setRequestProperty("Accept-Language", localeString)
// Add the `Accept-Language` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Accept-Language", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
+ requestHeadersBuilder.append(colonString)
requestHeadersBuilder.append(localeString)
httpUrlConnection.setRequestProperty("Cookie", cookiesString)
// Add the cookie header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Cookie", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
+ requestHeadersBuilder.append(colonString)
requestHeadersBuilder.append(cookiesString)
}
// `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding.
// Add the `Accept-Encoding` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
+ requestHeadersBuilder.append(newLineString)
requestHeadersBuilder.append("Accept-Encoding", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": gzip")
+ requestHeadersBuilder.append(colonString)
+ requestHeadersBuilder.append("gzip")
// Ignore SSL errors if requested.
if (ignoreSslErrors) {
// Get the response code, which causes the connection to the server to be made.
val responseCode = httpUrlConnection.responseCode
+ // Try to populate the SSL certificate information.
+ try {
+ // Get the applied cipher suite string.
+ val appliedCipherString = (httpUrlConnection as HttpsURLConnection).cipherSuite
+
+ // Populate the applied cipher builder, returned separately.
+ appliedCipherBuilder.append(appliedCipherString)
+
+ // Append the applied cipher suite to the SSL information builder.
+ sslInformationBuilder.append(application.getString(R.string.applied_cipher), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sslInformationBuilder.append(colonString)
+ sslInformationBuilder.append(appliedCipherString)
+ sslInformationBuilder.append(newLineString)
+
+ // Append the peer principal to the SSL information builder.
+ sslInformationBuilder.append(application.getString(R.string.peer_principal), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sslInformationBuilder.append(colonString)
+ sslInformationBuilder.append(httpUrlConnection.peerPrincipal.toString())
+ sslInformationBuilder.append(newLineString)
+
+ // Get the server certificate.
+ val serverCertificate = httpUrlConnection.serverCertificates[0]
+
+ // Append the certificate type to the SSL information builder.
+ sslInformationBuilder.append(application.getString(R.string.certificate_type), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sslInformationBuilder.append(colonString)
+ sslInformationBuilder.append(serverCertificate.type)
+ sslInformationBuilder.append(newLineString)
+
+ // Append the certificate hash code to the SSL information builder.
+ sslInformationBuilder.append(application.getString(R.string.certificate_hash_code), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ sslInformationBuilder.append(colonString)
+ sslInformationBuilder.append(serverCertificate.hashCode().toString())
+
+ // Get the available cipher suites string array.
+ val availableCipherSuitesStringArray = httpUrlConnection.sslSocketFactory.defaultCipherSuites
+
+ // Get the available cipher suites string array size.
+ val availableCipherSuitesStringArraySize = availableCipherSuitesStringArray.size
+
+ // Populate the available cipher suites, returned separately.
+ for (i in 0 until availableCipherSuitesStringArraySize) {
+ // Append a new line if a cipher is already populated.
+ if (i > 0)
+ availableCiphersBuilder.append(newLineString)
+
+ // Get the current cipher suite.
+ val currentCipherSuite = availableCipherSuitesStringArray[i]
+
+ // Append the current cipher to the list.
+ availableCiphersBuilder.append(currentCipherSuite)
+ }
+
+ // Populate the SSL certificate, returned separately.
+ sslCertificateBuilder.append(serverCertificate.toString())
+ } catch (exception: Exception) {
+ // Do nothing.
+ }
+
// Populate the response message string builder.
responseMessageBuilder.append(responseCode.toString(), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- responseMessageBuilder.append(": ")
+ responseMessageBuilder.append(colonString)
responseMessageBuilder.append(httpUrlConnection.responseMessage)
// Initialize the iteration variable.
while (httpUrlConnection.getHeaderField(i) != null) {
// Add a new line if there is already information in the string builder.
if (i > 0)
- responseHeadersBuilder.append(System.getProperty("line.separator"))
+ responseHeadersBuilder.append(newLineString)
// Add the header to the string builder and format the text.
responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- responseHeadersBuilder.append(": ")
+ responseHeadersBuilder.append(colonString)
responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i))
// Increment the iteration variable.
}
// Return the spannable string builders.
- return arrayOf(requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder)
+ return arrayOf(sslInformationBuilder, appliedCipherBuilder, availableCiphersBuilder, sslCertificateBuilder, requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder)
}
}
--- /dev/null
+/*
+ * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs
+
+import android.app.Dialog
+import android.graphics.Typeface
+import android.os.Bundle
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.StyleSpan
+import android.view.WindowManager
+
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.preference.PreferenceManager
+
+import com.stoutner.privacybrowser.R
+
+// Define the public class constants.
+const val AVAILABLE_CIPHERS = 0
+const val SSL_CERTIFICATE = 1
+
+// Define the private class constants.
+private const val DIALOG_TYPE = "A"
+private const val MESSAGE = "B"
+private const val APPLIED_CIPHER_STRING = "C"
+
+class ViewHeadersDetailDialog : DialogFragment() {
+ companion object {
+ fun displayDialog(dialogType: Int, message: String, appliedCipherString: String = ""): ViewHeadersDetailDialog {
+ // Create an arguments bundle.
+ val argumentsBundle = Bundle()
+
+ // Store the SSL error message components in the bundle.
+ argumentsBundle.putInt(DIALOG_TYPE, dialogType)
+ argumentsBundle.putString(MESSAGE, message)
+ argumentsBundle.putString(APPLIED_CIPHER_STRING, appliedCipherString)
+
+ // Create a new instance of the SSL certificate error dialog.
+ val thisHeadersSslCertificateDialog = ViewHeadersDetailDialog()
+
+ // Add the arguments bundle to the new dialog.
+ thisHeadersSslCertificateDialog.arguments = argumentsBundle
+
+ // Return the new dialog.
+ return thisHeadersSslCertificateDialog
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ // Get the arguments from the bundle.
+ val dialogType = requireArguments().getInt(DIALOG_TYPE)
+ val message = requireArguments().getString(MESSAGE)!!
+ val appliedCipherString = requireArguments().getString(APPLIED_CIPHER_STRING)!!
+
+ // Use a builder to create the alert dialog.
+ val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
+
+ // Set the icon according to the theme.
+ dialogBuilder.setIcon(R.drawable.ssl_certificate)
+
+ // Set the title and message according to the type.
+ if (dialogType == AVAILABLE_CIPHERS) { // A cipher suite dialog is displayed.
+ // Set the title
+ dialogBuilder.setTitle(R.string.available_ciphers)
+
+ // Create a message spannable string builder with the applied cipher bolded.
+ val messageSpannableStringBuilder = SpannableStringBuilder(message)
+
+ // Get the applied cipher index.
+ val appliedCipherIndex = message.indexOf(appliedCipherString)
+
+ // Set the applied cipher to be bold.
+ messageSpannableStringBuilder.setSpan(StyleSpan(Typeface.BOLD), appliedCipherIndex, appliedCipherIndex + appliedCipherString.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+
+ // Set the message.
+ dialogBuilder.setMessage(messageSpannableStringBuilder)
+ } else { // An SSL certificate dialog is displayed.
+ // Set the title and message.
+ dialogBuilder.setTitle(R.string.ssl_certificate)
+ dialogBuilder.setMessage(message)
+ }
+
+ // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else.
+ dialogBuilder.setNegativeButton(R.string.close, null)
+
+ // Create an alert dialog from the alert dialog builder.
+ val alertDialog = dialogBuilder.create()
+
+ // Get a handle for the shared preferences.
+ val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
+
+ // Get the screenshot preference.
+ val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+ // Disable screenshots if not allowed.
+ if (!allowScreenshots) {
+ alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ }
+
+ // Return the alert dialog.
+ return alertDialog
+ }
+}
package com.stoutner.privacybrowser.viewmodelfactories
+import android.app.Application
import android.content.ContentResolver
import androidx.lifecycle.ViewModel
import java.net.Proxy
import java.util.concurrent.ExecutorService
-class ViewHeadersFactory (private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy, private val contentResolver: ContentResolver,
- private val executorService: ExecutorService): ViewModelProvider.Factory {
+class ViewHeadersFactory (private val application: Application, private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy,
+ private val contentResolver: ContentResolver, private val executorService: ExecutorService): ViewModelProvider.Factory {
// Override the create function in order to add the provided arguments.
override fun <T: ViewModel> create(modelClass: Class<T>): T {
// Return a new instance of the model class with the provided arguments.
- return modelClass.getConstructor(String::class.java, String::class.java, String::class.java, Proxy::class.java, ContentResolver::class.java, ExecutorService::class.java)
- .newInstance(urlString, userAgent, localeString, proxy, contentResolver, executorService)
+ return modelClass.getConstructor(Application::class.java, String::class.java, String::class.java, String::class.java, Proxy::class.java, ContentResolver::class.java, ExecutorService::class.java)
+ .newInstance(application, urlString, userAgent, localeString, proxy, contentResolver, executorService)
}
}
package com.stoutner.privacybrowser.viewmodels
+import android.app.Application
import android.content.ContentResolver
import android.text.SpannableStringBuilder
+import androidx.lifecycle.AndroidViewModel
+
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
import com.stoutner.privacybrowser.backgroundtasks.GetHeadersBackgroundTask
import java.net.Proxy
import java.util.concurrent.ExecutorService
-class HeadersViewModel(private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy, private val contentResolver: ContentResolver,
- private val executorService: ExecutorService): ViewModel() {
+class HeadersViewModel(application: Application, private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy, private val contentResolver: ContentResolver,
+ private val executorService: ExecutorService): AndroidViewModel(application) {
// Initialize the mutable live data variables.
private val mutableLiveDataSourceStringArray = MutableLiveData<Array<SpannableStringBuilder>>()
private val mutableLiveDataErrorString = MutableLiveData<String>()
val getSourceBackgroundTask = GetHeadersBackgroundTask()
// Get the headers.
- executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, localeString, proxy, contentResolver, this,
+ executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(application, urlString, userAgent, localeString, proxy, contentResolver, this,
false)) }
}
val getSourceBackgroundTask = GetHeadersBackgroundTask()
// Get the headers.
- executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, localeString, proxy, contentResolver, this,
+ executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(getApplication(), urlString, userAgent, localeString, proxy, contentResolver, this,
ignoreSslErrors)) }
}
}
android:orientation="vertical"
android:layout_margin="10dp" >
+ <!-- SSL information. -->
+ <TextView
+ android:id="@+id/ssl_information_title_textview"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@string/ssl_information"
+ android:textAlignment="center"
+ android:textSize="18sp"
+ android:textColor="@color/blue_text"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/ssl_information_textview"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:textIsSelectable="true"
+ android:layout_marginBottom="8dp" />
+
+ <!-- Button row. -->
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginBottom="16dp"
+ android:layout_gravity="center_horizontal" >
+
+ <Button
+ android:id="@+id/ciphers_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/ciphers"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginEnd="10dp"
+ android:onClick="showCiphers"
+ app:backgroundTint="@color/button_background_selector"
+ android:textColor="@color/button_text_selector"
+ tools:ignore="ButtonStyle" />
+
+ <Button
+ android:id="@+id/certificate_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/certificate"
+ android:onClick="showCertificate"
+ app:backgroundTint="@color/button_background_selector"
+ android:textColor="@color/button_text_selector"
+ tools:ignore="ButtonStyle" />
+ </LinearLayout>
+
<!-- Request headers. -->
<TextView
android:id="@+id/request_headers_title_textview"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorlayout"
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_margin="10dp" >
+ <!-- SSL information. -->
+ <TextView
+ android:id="@+id/ssl_information_title_textview"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@string/ssl_information"
+ android:textAlignment="center"
+ android:textSize="18sp"
+ android:textColor="@color/blue_text"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/ssl_information_textview"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:textIsSelectable="true"
+ android:layout_marginBottom="8dp" />
+
+ <!-- Button row. -->
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginBottom="16dp"
+ android:layout_gravity="center_horizontal" >
+
+ <Button
+ android:id="@+id/ciphers_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/ciphers"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginEnd="10dp"
+ android:onClick="showCiphers"
+ app:backgroundTint="@color/button_background_selector"
+ android:textColor="@color/button_text_selector"
+ tools:ignore="ButtonStyle" />
+
+ <Button
+ android:id="@+id/certificate_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/certificate"
+ android:onClick="showCertificate"
+ app:backgroundTint="@color/button_background_selector"
+ android:textColor="@color/button_text_selector"
+ tools:ignore="ButtonStyle" />
+ </LinearLayout>
+
<!-- Request headers. -->
<TextView
android:id="@+id/request_headers_title_textview"
<string name="error_saving_file">Fehler beim Speichern der Datei %1$s:\u0020 %2$s</string>
<string name="unknown_error">Unbekannter Fehler</string>
- <!-- View Headers. -->
+ <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+ <string name="colon">: \u0020</string>
<string name="request_headers">Anfragekopfzeilen</string>
<string name="response_message">Status-Code</string>
<string name="response_headers">Antwortkopfzeilen</string>
<string name="error_saving_file">Error al guardar %1$s:\u0020 %2$s</string>
<string name="unknown_error">Error desconocido</string>
- <!-- View Headers. -->
+ <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+ <string name="colon">: \u0020</string>
<string name="request_headers">Cabeceras de solicitud</string>
<string name="response_message">Mensaje de respuesta</string>
<string name="response_headers">Cabeceras de respuesta</string>
<string name="error_saving_file">Erreur lors de l\'enregistrement de %1$s : %2$s</string>
<string name="unknown_error">Erreur inconnue</string>
- <!-- View Headers. -->
+ <!-- View Headers. Android removes initial and trailing spaces, but they can be manually specified with the Unicode `\u0020` formatting.-->
+ <string name="colon">\u0020:\u0020</string>
<string name="request_headers">En-tête de la requête</string>
<string name="response_message">Message de la réponse</string>
<string name="response_headers">En-tête de la réponse</string>
<string name="error_saving_file">Erro ao salvar %1$s:\u0020 %2$s</string>
<string name="unknown_error">Erro desconhecido</string>
- <!-- View Headers. -->
+ <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+ <string name="colon">: \u0020</string>
<string name="request_headers">Solicitar cabeçalhos</string>
<string name="response_message">Mensagem de Resposta</string>
<string name="response_headers">Cabeçalhos de resposta</string>
<string name="error_saving_file">Ошибка сохранения %1$s:\u0020 %2$s</string>
<string name="unknown_error">Неизвестная ошибка</string>
- <!-- View Headers. -->
+ <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+ <string name="colon">: \u0020</string>
<string name="request_headers">Заголовки запроса</string>
<string name="response_message">Ответное сообщение</string>
<string name="response_headers">Заголовки ответа</string>
<string name="file_name">Dosya adı</string>
<string name="unknown_size">Bilinmeyen boyut</string>
- <!-- View Headers. -->
+ <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+ <string name="colon">: \u0020</string>
<string name="request_headers">İstek Başlıkları</string>
<string name="response_message">Yanıt Mesajı</string>
<string name="response_headers">Yanıt Başlıkları</string>
<string name="error_saving_file">保存失败 %1$s:\u0020 %2$s</string>
<string name="unknown_error">未知错误</string>
- <!-- View Headers. -->
+ <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+ <string name="colon">: \u0020</string>
<string name="request_headers">请求头</string>
<string name="response_message">响应</string>
<string name="response_headers">响应头</string>
<string name="error_saving_file">Error saving %1$s:\u0020 %2$s</string>
<string name="unknown_error">Unknown error</string>
- <!-- View Headers. -->
+ <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+ <string name="colon">: \u0020</string>
+ <string name="ssl_information">SSL Information</string>
+ <string name="applied_cipher">Applied Cipher</string>
+ <string name="peer_principal">Peer Principal</string>
+ <string name="certificate_type">Certificate Type</string>
+ <string name="certificate_hash_code">Certificate Hash Code</string>
+ <string name="ciphers">Ciphers</string>
+ <string name="available_ciphers">Available Ciphers</string>
+ <string name="certificate">Certificate</string>
<string name="request_headers">Request Headers</string>
<string name="response_message">Response Message</string>
<string name="response_headers">Response Headers</string>