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