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