]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/ViewHeadersActivity.kt
Add a flow layout to the Headers SSL buttons.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / ViewHeadersActivity.kt
1 /*
2  * Copyright 2017-2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser Android is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.activities
21
22 import android.content.ClipData
23 import android.content.ClipboardManager
24 import android.content.Context
25 import android.content.Intent
26 import android.net.Uri
27 import android.os.Build
28 import android.os.Bundle
29 import android.provider.OpenableColumns
30 import android.text.SpannableStringBuilder
31 import android.text.style.ForegroundColorSpan
32 import android.util.TypedValue
33 import android.view.KeyEvent
34 import android.view.Menu
35 import android.view.MenuItem
36 import android.view.View
37 import android.view.View.OnFocusChangeListener
38 import android.view.WindowManager
39 import android.view.inputmethod.InputMethodManager
40 import android.widget.EditText
41 import android.widget.ProgressBar
42 import android.widget.TextView
43
44 import androidx.activity.result.contract.ActivityResultContracts
45 import androidx.appcompat.app.ActionBar
46 import androidx.appcompat.app.AppCompatActivity
47 import androidx.appcompat.widget.Toolbar
48 import androidx.constraintlayout.widget.ConstraintLayout
49 import androidx.core.app.NavUtils
50 import androidx.lifecycle.ViewModelProvider
51 import androidx.preference.PreferenceManager
52 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
53
54 import com.google.android.material.snackbar.Snackbar
55
56 import com.stoutner.privacybrowser.R
57 import com.stoutner.privacybrowser.dialogs.AVAILABLE_CIPHERS
58 import com.stoutner.privacybrowser.dialogs.SSL_CERTIFICATE
59 import com.stoutner.privacybrowser.dialogs.AboutViewHeadersDialog
60 import com.stoutner.privacybrowser.dialogs.ViewHeadersDetailDialog
61 import com.stoutner.privacybrowser.dialogs.UntrustedSslCertificateDialog
62 import com.stoutner.privacybrowser.dialogs.UntrustedSslCertificateDialog.UntrustedSslCertificateListener
63 import com.stoutner.privacybrowser.helpers.ProxyHelper
64 import com.stoutner.privacybrowser.helpers.UrlHelper
65 import com.stoutner.privacybrowser.viewmodelfactories.ViewHeadersFactory
66 import com.stoutner.privacybrowser.viewmodels.HeadersViewModel
67
68 import kotlinx.coroutines.CoroutineScope
69 import kotlinx.coroutines.Dispatchers
70 import kotlinx.coroutines.launch
71 import kotlinx.coroutines.withContext
72
73 import java.lang.Exception
74 import java.nio.charset.StandardCharsets
75
76 // Define the public constants.
77 const val USER_AGENT = "user_agent"
78
79 class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener {
80     // Declare the class variables.
81     private lateinit var appliedCipherString: String
82     private lateinit var availableCiphersString: String
83     private lateinit var headersViewModel: HeadersViewModel
84     private lateinit var initialGrayColorSpan: ForegroundColorSpan
85     private lateinit var finalGrayColorSpan: ForegroundColorSpan
86     private lateinit var redColorSpan: ForegroundColorSpan
87     private lateinit var sslCertificateString: String
88
89     // Declare the class views.
90     private lateinit var urlEditText: EditText
91     private lateinit var sslInformationTitleTextView: TextView
92     private lateinit var sslInformationTextView: TextView
93     private lateinit var sslButtonsConstraintLayout: ConstraintLayout
94     private lateinit var requestHeadersTitleTextView: TextView
95     private lateinit var requestHeadersTextView: TextView
96     private lateinit var responseMessageTitleTextView: TextView
97     private lateinit var responseMessageTextView: TextView
98     private lateinit var responseHeadersTitleTextView: TextView
99     private lateinit var responseHeadersTextView: TextView
100     private lateinit var responseBodyTitleTextView: TextView
101     private lateinit var responseBodyTextView: TextView
102
103     // Define the save text activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
104     private val saveTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri ->
105         // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
106         if (fileUri != null) {
107             // Initialize the file name string from the file URI last path segment.
108             var fileNameString = fileUri.lastPathSegment
109
110             // Query the exact file name if the API >= 26.
111             if (Build.VERSION.SDK_INT >= 26) {
112                 // Get a cursor from the content resolver.
113                 val contentResolverCursor = contentResolver.query(fileUri, null, null, null)!!
114
115                 // Move to the first row.
116                 contentResolverCursor.moveToFirst()
117
118                 // Get the file name from the cursor.
119                 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
120
121                 // Close the cursor.
122                 contentResolverCursor.close()
123             }
124
125             try {
126                 // Get the about version string.
127                 val headersString = getHeadersString()
128
129                 // Open an output stream.
130                 val outputStream = contentResolver.openOutputStream(fileUri)!!
131
132                 // Save the headers using a coroutine with Dispatchers.IO.
133                 CoroutineScope(Dispatchers.Main).launch {
134                     withContext(Dispatchers.IO) {
135                         // Write the headers string to the output stream.
136                         outputStream.write(headersString.toByteArray(StandardCharsets.UTF_8))
137
138                         // Close the output stream.
139                         outputStream.close()
140                     }
141                 }
142
143                 // Display a snackbar with the saved logcat information.
144                 Snackbar.make(urlEditText, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
145             } catch (exception: Exception) {
146                 // Display a snackbar with the error message.
147                 Snackbar.make(urlEditText, getString(R.string.error_saving_file, fileNameString, exception.toString()), Snackbar.LENGTH_INDEFINITE).show()
148             }
149         }
150     }
151
152     override fun onCreate(savedInstanceState: Bundle?) {
153         // Get a handle for the shared preferences.
154         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
155
156         // Get the preferences.
157         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
158         val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
159
160         // Disable screenshots if not allowed.
161         if (!allowScreenshots) {
162             window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
163         }
164
165         // Run the default commands.
166         super.onCreate(savedInstanceState)
167
168         // Get the launching intent
169         val intent = intent
170
171         // Get the information from the intent.
172         val currentUrl = intent.getStringExtra(CURRENT_URL)!!
173         val userAgent = intent.getStringExtra(USER_AGENT)!!
174
175         // Set the content view.
176         if (bottomAppBar) {
177             setContentView(R.layout.view_headers_bottom_appbar)
178         } else {
179             setContentView(R.layout.view_headers_top_appbar)
180         }
181
182         // Get a handle for the toolbar.
183         val toolbar = findViewById<Toolbar>(R.id.toolbar)
184
185         // Set the support action bar.
186         setSupportActionBar(toolbar)
187
188         // Get a handle for the action bar.
189         val actionBar = supportActionBar!!
190
191         // Add the custom layout to the action bar.
192         actionBar.setCustomView(R.layout.view_headers_appbar_custom_view)
193
194         // Instruct the action bar to display a custom layout.
195         actionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
196
197         // Get handles for the views.
198         urlEditText = findViewById(R.id.url_edittext)
199         val progressBar = findViewById<ProgressBar>(R.id.progress_bar)
200         val swipeRefreshLayout = findViewById<SwipeRefreshLayout>(R.id.swiperefreshlayout)
201         sslInformationTitleTextView = findViewById(R.id.ssl_information_title_textview)
202         sslInformationTextView = findViewById(R.id.ssl_information_textview)
203         sslButtonsConstraintLayout = findViewById(R.id.ssl_buttons_constraintlayout)
204         requestHeadersTitleTextView = findViewById(R.id.request_headers_title_textview)
205         requestHeadersTextView = findViewById(R.id.request_headers_textview)
206         responseMessageTitleTextView = findViewById(R.id.response_message_title_textview)
207         responseMessageTextView = findViewById(R.id.response_message_textview)
208         responseHeadersTitleTextView = findViewById(R.id.response_headers_title_textview)
209         responseHeadersTextView = findViewById(R.id.response_headers_textview)
210         responseBodyTitleTextView = findViewById(R.id.response_body_title_textview)
211         responseBodyTextView = findViewById(R.id.response_body_textview)
212
213         // Initialize the gray foreground color spans for highlighting the URLs.
214         initialGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500))
215         finalGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500))
216         redColorSpan = ForegroundColorSpan(getColor(R.color.red_text))
217
218         // Get a handle for the input method manager, which is used to hide the keyboard.
219         val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
220
221         // Remove the formatting from the URL when the user is editing the text.
222         urlEditText.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean ->
223             if (hasFocus) {  // The user is editing the URL text box.
224                 // Get the foreground color spans.
225                 val foregroundColorSpans: Array<ForegroundColorSpan> = urlEditText.text.getSpans(0, urlEditText.text.length, ForegroundColorSpan::class.java)
226
227                 // Remove each foreground color span that highlights the text.
228                 for (foregroundColorSpan in foregroundColorSpans)
229                     urlEditText.text.removeSpan(foregroundColorSpan)
230             } else {  // The user has stopped editing the URL text box.
231                 // Hide the soft keyboard.
232                 inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0)
233
234                 // Move to the beginning of the string.
235                 urlEditText.setSelection(0)
236
237                 // Store the URL text in the intent, so update layout uses the new text if the app is restarted.
238                 intent.putExtra(CURRENT_URL, urlEditText.text.toString())
239
240                 // Reapply the highlighting.
241                 UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
242             }
243         }
244
245         // Populate the URL text box.
246         urlEditText.setText(currentUrl)
247
248         // Apply the initial text highlighting to the URL.
249         UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
250
251         // Set the refresh color scheme according to the theme.
252         swipeRefreshLayout.setColorSchemeResources(R.color.blue_text)
253
254         // Initialize a color background typed value.
255         val colorBackgroundTypedValue = TypedValue()
256
257         // Get the color background from the theme.
258         theme.resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true)
259
260         // Get the color background int from the typed value.
261         val colorBackgroundInt = colorBackgroundTypedValue.data
262
263         // Set the swipe refresh background color.
264         swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt)
265
266         // Get the list of locales.
267         val localeList = resources.configuration.locales
268
269         // Initialize a string builder to extract the locales from the list.
270         val localesStringBuilder = StringBuilder()
271
272         // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
273         var q = 10
274
275         // Populate the string builder with the contents of the locales list.
276         for (i in 0 until localeList.size()) {
277             // Append a comma if there is already an item in the string builder.
278             if (i > 0) {
279                 localesStringBuilder.append(",")
280             }
281
282             // Get the locale from the list.
283             val locale = localeList[i]
284
285             // Add the locale to the string.  `locale` by default displays as `en_US`, but WebView uses the `en-US` format.
286             localesStringBuilder.append(locale.language)
287             localesStringBuilder.append("-")
288             localesStringBuilder.append(locale.country)
289
290             // If not the first locale, append `;q=0.x`, which drops by .1 for each removal from the main locale until q=0.1.
291             if (q < 10) {
292                 localesStringBuilder.append(";q=0.")
293                 localesStringBuilder.append(q)
294             }
295
296             // Decrement `q` if it is greater than 1.
297             if (q > 1) {
298                 q--
299             }
300
301             // Add a second entry for the language only portion of the locale.
302             localesStringBuilder.append(",")
303             localesStringBuilder.append(locale.language)
304
305             // Append `1;q=0.x`, which drops by .1 for each removal form the main locale until q=0.1.
306             localesStringBuilder.append(";q=0.")
307             localesStringBuilder.append(q)
308
309             // Decrement `q` if it is greater than 1.
310             if (q > 1) {
311                 q--
312             }
313         }
314
315         // Instantiate the proxy helper.
316         val proxyHelper = ProxyHelper()
317
318         // Get the current proxy.
319         val proxy = proxyHelper.getCurrentProxy(this)
320
321         // Make the progress bar visible.
322         progressBar.visibility = View.VISIBLE
323
324         // Set the progress bar to be indeterminate.
325         progressBar.isIndeterminate = true
326
327         // Update the layout.
328         updateLayout(currentUrl)
329
330         // Instantiate the view headers factory.
331         val viewHeadersFactory: ViewModelProvider.Factory = ViewHeadersFactory(application, currentUrl, userAgent, localesStringBuilder.toString(), proxy, contentResolver, MainWebViewActivity.executorService)
332
333         // Instantiate the headers view model.
334         headersViewModel = ViewModelProvider(this, viewHeadersFactory)[HeadersViewModel::class.java]
335
336         // Create a headers observer.
337         headersViewModel.observeHeaders().observe(this) { headersStringArray: Array<SpannableStringBuilder> ->
338             // Populate the text views.  This can take a long time, and freezes the user interface, if the response body is particularly large.
339             sslInformationTextView.text = headersStringArray[0]
340             requestHeadersTextView.text = headersStringArray[4]
341             responseMessageTextView.text = headersStringArray[5]
342             responseHeadersTextView.text = headersStringArray[6]
343             responseBodyTextView.text = headersStringArray[7]
344
345             // Populate the dialog strings.
346             appliedCipherString = headersStringArray[1].toString()
347             availableCiphersString = headersStringArray[2].toString()
348             sslCertificateString = headersStringArray[3].toString()
349
350             // Hide the progress bar.
351             progressBar.isIndeterminate = false
352             progressBar.visibility = View.GONE
353
354             // Stop the swipe to refresh indicator if it is running
355             swipeRefreshLayout.isRefreshing = false
356         }
357
358         // Create an error observer.
359         headersViewModel.observeErrors().observe(this) { errorString: String ->
360             // Display an error snackbar if the string is not `""`.
361             if (errorString != "") {
362                 if (errorString.startsWith("javax.net.ssl.SSLHandshakeException")) {
363                     // Instantiate the untrusted SSL certificate dialog.
364                     val untrustedSslCertificateDialog = UntrustedSslCertificateDialog()
365
366                     // Show the untrusted SSL certificate dialog.
367                     untrustedSslCertificateDialog.show(supportFragmentManager, getString(R.string.invalid_certificate))
368                 } else {
369                     // Display a snackbar with the error message.
370                     Snackbar.make(swipeRefreshLayout, errorString, Snackbar.LENGTH_LONG).show()
371                 }
372             }
373         }
374
375         // Implement swipe to refresh.
376         swipeRefreshLayout.setOnRefreshListener {
377             // Make the progress bar visible.
378             progressBar.visibility = View.VISIBLE
379
380             // Set the progress bar to be indeterminate.
381             progressBar.isIndeterminate = true
382
383             // Get the URL.
384             val urlString = urlEditText.text.toString()
385
386             // Update the layout.
387             updateLayout(urlString)
388
389             // Get the updated headers.
390             headersViewModel.updateHeaders(urlString, false)
391         }
392
393         // Set the go button on the keyboard to request new headers data.
394         urlEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
395             // Request new headers data if the enter key was pressed.
396             if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
397                 // Hide the soft keyboard.
398                 inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0)
399
400                 // Remove the focus from the URL box.
401                 urlEditText.clearFocus()
402
403                 // Make the progress bar visible.
404                 progressBar.visibility = View.VISIBLE
405
406                 // Set the progress bar to be indeterminate.
407                 progressBar.isIndeterminate = true
408
409                 // Get the URL.
410                 val urlString = urlEditText.text.toString()
411
412                 // Update the layout.
413                 updateLayout(urlString)
414
415                 // Get the updated headers.
416                 headersViewModel.updateHeaders(urlString, false)
417
418                 // Consume the key press.
419                 return@setOnKeyListener true
420             } else {
421                 // Do not consume the key press.
422                 return@setOnKeyListener false
423             }
424         }
425     }
426
427     override fun onCreateOptionsMenu(menu: Menu): Boolean {
428         // Inflate the menu.
429         menuInflater.inflate(R.menu.view_headers_options_menu, menu)
430
431         // Display the menu.
432         return true
433     }
434
435     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
436         // Run the appropriate commands.
437         when (menuItem.itemId) {
438             R.id.copy_headers -> {  // Copy the headers.
439                 // Get the headers string.
440                 val headersString = getHeadersString()
441
442                 // Get a handle for the clipboard manager.
443                 val clipboardManager = (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
444
445                 // Place the headers string in a clip data.
446                 val headersClipData = ClipData.newPlainText(getString(R.string.view_headers), headersString)
447
448                 // Place the clip data on the clipboard.
449                 clipboardManager.setPrimaryClip(headersClipData)
450
451                 // Display a snackbar if the API <= 32 (Android 12L).  Beginning in Android 13 the OS displays a notification that covers up the snackbar.
452                 if (Build.VERSION.SDK_INT <= 32)
453                     Snackbar.make(urlEditText, R.string.headers_copied, Snackbar.LENGTH_SHORT).show()
454
455                 // Consume the event.
456                 return true
457             }
458
459             R.id.share_headers -> {  // Share the headers.
460                 // Get the headers string.
461                 val headersString = getHeadersString()
462
463                 // Create a share intent.
464                 val shareIntent = Intent(Intent.ACTION_SEND)
465
466                 // Add the headers string to the intent.
467                 shareIntent.putExtra(Intent.EXTRA_TEXT, headersString)
468
469                 // Set the MIME type.
470                 shareIntent.type = "text/plain"
471
472                 // Set the intent to open in a new task.
473                 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
474
475                 // Make it so.
476                 startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
477
478                 // Consume the event.
479                 return true
480             }
481
482             R.id.save_headers -> {  // Save the headers as a text file.
483                 // Get the current URL.
484                 val currentUrlString = urlEditText.text.toString()
485
486                 // Get a URI for the current URL.
487                 val currentUri = Uri.parse(currentUrlString)
488
489                 // Get the current domain name.
490                 val currentDomainName = currentUri.host
491
492                 // Open the file picker.
493                 saveTextActivityResultLauncher.launch(getString(R.string.headers_txt, currentDomainName))
494
495                 // Consume the event.
496                 return true
497             }
498
499             R.id.about_view_headers -> {  // Display the about dialog.
500                 // Instantiate the about dialog fragment.
501                 val aboutDialogFragment = AboutViewHeadersDialog()
502
503                 // Show the about alert dialog.
504                 aboutDialogFragment.show(supportFragmentManager, getString(R.string.about))
505
506                 // Consume the event.
507                 return true
508             }
509
510             else -> {  // The home button was selected.
511                 // Run the parents class on return.
512                 return super.onOptionsItemSelected(menuItem)
513             }
514         }
515     }
516
517     // This method must be named `goBack()` and must have a View argument to match the default back arrow in the app bar or a crash occurs.
518     fun goBack(@Suppress("UNUSED_PARAMETER") view: View) {
519         // Go home.
520         NavUtils.navigateUpFromSameTask(this)
521     }
522
523     private fun getHeadersString(): String {
524         // Initialize a headers string builder.
525         val headersStringBuilder = StringBuilder()
526
527         // Populate the SSL information if it is visible (an HTTPS URL is loaded).
528         if (sslInformationTitleTextView.visibility == View.VISIBLE) {
529             headersStringBuilder.append(sslInformationTitleTextView.text)
530             headersStringBuilder.append("\n")
531             headersStringBuilder.append(sslInformationTextView.text)
532             headersStringBuilder.append("\n\n")
533             headersStringBuilder.append(getString(R.string.available_ciphers))
534             headersStringBuilder.append("\n")
535             headersStringBuilder.append(availableCiphersString)
536             headersStringBuilder.append("\n\n")
537             headersStringBuilder.append(getString(R.string.ssl_certificate))
538             headersStringBuilder.append("\n")
539             headersStringBuilder.append(sslCertificateString)
540             headersStringBuilder.append("\n")  // Only a single new line is needed after the certificate as it already ends in one.
541         }
542
543         // Populate the request information if it is visible (an HTTP URL is loaded).
544         if (requestHeadersTitleTextView.visibility == View.VISIBLE) {
545             headersStringBuilder.append(requestHeadersTitleTextView.text)
546             headersStringBuilder.append("\n")
547             headersStringBuilder.append(requestHeadersTextView.text)
548             headersStringBuilder.append("\n\n")
549             headersStringBuilder.append(responseMessageTitleTextView.text)
550             headersStringBuilder.append("\n")
551             headersStringBuilder.append(responseMessageTextView.text)
552             headersStringBuilder.append("\n\n")
553         }
554
555         // Populate the response information, which is visible for both HTTP and content URLs.
556         headersStringBuilder.append(responseHeadersTitleTextView.text)
557         headersStringBuilder.append("\n")
558         headersStringBuilder.append(responseHeadersTextView.text)
559         headersStringBuilder.append("\n\n")
560         headersStringBuilder.append(responseBodyTitleTextView.text)
561         headersStringBuilder.append("\n")
562         headersStringBuilder.append(responseBodyTextView.text)
563
564         // Return the string.
565         return headersStringBuilder.toString()
566     }
567
568     override fun loadAnyway() {
569         // Load the URL anyway.
570         headersViewModel.updateHeaders(urlEditText.text.toString(), true)
571     }
572
573     // The view parameter cannot be removed because it is called from the layout onClick.
574     fun showCertificate(@Suppress("UNUSED_PARAMETER")view: View) {
575         // Instantiate an SSL certificate dialog.
576         val sslCertificateDialogFragment= ViewHeadersDetailDialog.displayDialog(SSL_CERTIFICATE, sslCertificateString)
577
578         // Show the dialog.
579         sslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate))
580     }
581
582     // The view parameter cannot be removed because it is called from the layout onClick.
583     fun showCiphers(@Suppress("UNUSED_PARAMETER")view: View) {
584         // Instantiate an SSL certificate dialog.
585         val ciphersDialogFragment= ViewHeadersDetailDialog.displayDialog(AVAILABLE_CIPHERS, availableCiphersString, appliedCipherString)
586
587         // Show the dialog.
588         ciphersDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate))
589     }
590
591     private fun updateLayout(urlString: String) {
592         if (urlString.startsWith("content://")) {  // This is a content URL.
593             // Hide the unused views.
594             sslInformationTitleTextView.visibility = View.GONE
595             sslInformationTextView.visibility = View.GONE
596             sslButtonsConstraintLayout.visibility = View.GONE
597             requestHeadersTitleTextView.visibility = View.GONE
598             requestHeadersTextView.visibility = View.GONE
599             responseMessageTitleTextView.visibility = View.GONE
600             responseMessageTextView.visibility = View.GONE
601
602             // Change the text of the remaining title text views.
603             responseHeadersTitleTextView.setText(R.string.content_metadata)
604             responseBodyTitleTextView.setText(R.string.content_data)
605         } else {  // This is not a content URL.
606             // Set the status if the the SSL information views.
607             if (urlString.startsWith("http://")) {  // This is an HTTP URL.
608                 // Hide the SSL information views.
609                 sslInformationTitleTextView.visibility = View.GONE
610                 sslInformationTextView.visibility = View.GONE
611                 sslButtonsConstraintLayout.visibility = View.GONE
612             } else {  // This is not an HTTP URL.
613                 // Show the SSL information views.
614                 sslInformationTitleTextView.visibility = View.VISIBLE
615                 sslInformationTextView.visibility = View.VISIBLE
616                 sslButtonsConstraintLayout.visibility = View.VISIBLE
617             }
618
619             // Show the other views.
620             requestHeadersTitleTextView.visibility = View.VISIBLE
621             requestHeadersTextView.visibility = View.VISIBLE
622             responseMessageTitleTextView.visibility = View.VISIBLE
623             responseMessageTextView.visibility = View.VISIBLE
624
625             // Restore the text of the other title text views.
626             responseHeadersTitleTextView.setText(R.string.response_headers)
627             responseBodyTitleTextView.setText(R.string.response_body)
628         }
629     }
630 }