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