]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt
949b678a90e06cd325072b97dc0618d02a8ed818
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / DomainsActivity.kt
1 /*
2  * Copyright 2017-2022 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.app.Activity
23 import android.content.Context
24 import android.database.Cursor
25 import android.os.Bundle
26 import android.os.Handler
27 import android.view.Menu
28 import android.view.MenuItem
29 import android.view.View
30 import android.view.ViewGroup
31 import android.view.WindowManager
32 import android.widget.EditText
33 import android.widget.ListView
34 import android.widget.RadioButton
35 import android.widget.ScrollView
36 import android.widget.Spinner
37 import android.widget.TextView
38 import androidx.activity.OnBackPressedCallback
39
40 import androidx.appcompat.app.AppCompatActivity
41 import androidx.appcompat.widget.SwitchCompat
42 import androidx.appcompat.widget.Toolbar
43 import androidx.core.app.NavUtils
44 import androidx.cursoradapter.widget.CursorAdapter
45 import androidx.fragment.app.DialogFragment
46 import androidx.preference.PreferenceManager
47
48 import com.google.android.material.floatingactionbutton.FloatingActionButton
49 import com.google.android.material.snackbar.Snackbar
50
51 import com.stoutner.privacybrowser.R
52 import com.stoutner.privacybrowser.dialogs.AddDomainDialog.AddDomainListener
53 import com.stoutner.privacybrowser.dialogs.AddDomainDialog.Companion.addDomain
54 import com.stoutner.privacybrowser.fragments.DomainSettingsFragment
55 import com.stoutner.privacybrowser.fragments.DomainsListFragment
56 import com.stoutner.privacybrowser.fragments.DomainsListFragment.DismissSnackbarInterface
57 import com.stoutner.privacybrowser.fragments.DomainsListFragment.SaveDomainSettingsInterface
58 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
59
60 // Define the class constants.
61 private const val DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_id"
62 private const val DOMAIN_SETTINGS_DISPLAYED = "domain_settings_displayed"
63 private const val DOMAIN_SETTINGS_SCROLL_Y = "domain_settings_scroll_y"
64 private const val LISTVIEW_POSITION = "listview_position"
65
66 class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarInterface, SaveDomainSettingsInterface {
67     companion object {
68         // Define the public constants.
69         const val CLOSE_ON_BACK = "close_on_back"
70         const val CURRENT_IP_ADDRESSES = "current_ip_addresses"
71         const val CURRENT_URL = "current_url"
72         const val LOAD_DOMAIN = "load_domain"
73         const val SSL_END_DATE = "ssl_end_date"
74         const val SSL_ISSUED_BY_CNAME = "ssl_issued_by_cname"
75         const val SSL_ISSUED_BY_ONAME = "ssl_issued_by_oname"
76         const val SSL_ISSUED_BY_UNAME = "ssl_issued_by_uname"
77         const val SSL_ISSUED_TO_CNAME = "ssl_issued_to_cname"
78         const val SSL_ISSUED_TO_ONAME = "ssl_issued_to_oname"
79         const val SSL_ISSUED_TO_UNAME = "ssl_issued_to_uname"
80         const val SSL_START_DATE = "ssl_start_date"
81
82         // Define the public variables.
83         var currentDomainDatabaseId = 0  // Used in `DomainsListFragment`.
84         var dismissingSnackbar = false  // Used in `DomainsListFragment`.
85         var domainsListViewPosition = 0  // Used in `DomainsListFragment`.
86         var sslEndDateLong: Long = 0  // Used in `DomainsSettingsFragment`.
87         var sslStartDateLong: Long = 0  // Used in `DomainSettingsFragment`.
88         var twoPanedMode = false  // Used in `DomainsListFragment`.
89
90         // Declare the public views.  They are used in `DomainsListFragment`.
91         lateinit var deleteMenuItem: MenuItem
92
93         // Declare the SSL certificate and IP address strings.
94         var currentIpAddresses: String? = null  // Used in `DomainSettingsFragment`.
95         var sslIssuedToCName: String? = null  // Used in `DomainSettingsFragment`.
96         var sslIssuedToOName: String? = null  // Used in `DomainSettingsFragment`.
97         var sslIssuedToUName: String? = null  // Used in `DomainSettingsFragment`.
98         var sslIssuedByCName: String? = null  // Used in `DomainSettingsFragment`.
99         var sslIssuedByOName: String? = null  // Used in `DomainSettingsFragment`.
100         var sslIssuedByUName: String? = null  // Used in `DomainSettingsFragment`.
101     }
102
103     // Declare the class views.
104     private lateinit var addDomainFAB: FloatingActionButton
105     private lateinit var coordinatorLayout: View
106     private var domainsListView: ListView? = null
107     private var undoDeleteSnackbar: Snackbar? = null
108
109     // Declare the class variables.
110     private lateinit var domainsDatabaseHelper: DomainsDatabaseHelper
111
112     // Define the class variables.
113     private var appRestarted = false
114     private var closeActivityAfterDismissingSnackbar = false
115     private var closeOnBack = false
116     private var deletedDomainPosition = 0
117     private var domainSettingsDatabaseIdBeforeRestart = 0
118     private var domainSettingsDisplayedBeforeRestart = false
119     private var domainSettingsScrollY = 0
120     private var goDirectlyToDatabaseId = -1
121
122     override fun onCreate(savedInstanceState: Bundle?) {
123         // Get a handle for the shared preferences.
124         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
125
126         // Get the preferences.
127         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
128         val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
129
130         // Disable screenshots if not allowed.
131         if (!allowScreenshots) {
132             window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
133         }
134
135         // Run the default commands.
136         super.onCreate(savedInstanceState)
137
138         // Process the saved instance state if it is not null.
139         if (savedInstanceState != null) {
140             // Extract the values from the saved instance state if it is not null.
141             domainsListViewPosition = savedInstanceState.getInt(LISTVIEW_POSITION)
142             domainSettingsDisplayedBeforeRestart = savedInstanceState.getBoolean(DOMAIN_SETTINGS_DISPLAYED)
143             domainSettingsDatabaseIdBeforeRestart = savedInstanceState.getInt(DOMAIN_SETTINGS_DATABASE_ID)
144             domainSettingsScrollY = savedInstanceState.getInt(DOMAIN_SETTINGS_SCROLL_Y)
145
146             // Set the app restarted flag.
147             appRestarted = true
148         }
149
150         // Get the launching intent
151         val intent = intent
152
153         // Extract the domain to load if there is one.  `-1` is the default value.
154         goDirectlyToDatabaseId = intent.getIntExtra(LOAD_DOMAIN, -1)
155
156         // Get the status of close-on-back, which is true when the domains activity is called from the options menu.
157         closeOnBack = intent.getBooleanExtra(CLOSE_ON_BACK, false)
158
159         // Get the current URL.
160         val currentUrl = intent.getStringExtra(CURRENT_URL)
161
162         // Store the current SSL certificate information in class variables.
163         sslIssuedToCName = intent.getStringExtra(SSL_ISSUED_TO_CNAME)
164         sslIssuedToOName = intent.getStringExtra(SSL_ISSUED_TO_ONAME)
165         sslIssuedToUName = intent.getStringExtra(SSL_ISSUED_TO_UNAME)
166         sslIssuedByCName = intent.getStringExtra(SSL_ISSUED_BY_CNAME)
167         sslIssuedByOName = intent.getStringExtra(SSL_ISSUED_BY_ONAME)
168         sslIssuedByUName = intent.getStringExtra(SSL_ISSUED_BY_UNAME)
169         sslStartDateLong = intent.getLongExtra(SSL_START_DATE, 0)
170         sslEndDateLong = intent.getLongExtra(SSL_END_DATE, 0)
171         currentIpAddresses = intent.getStringExtra(CURRENT_IP_ADDRESSES)
172
173         // Set the content view.
174         if (bottomAppBar)
175             setContentView(R.layout.domains_bottom_appbar)
176         else
177             setContentView(R.layout.domains_top_appbar)
178
179         // Get handles for the views.
180         coordinatorLayout = findViewById(R.id.domains_coordinatorlayout)
181         val toolbar = findViewById<Toolbar>(R.id.domains_toolbar)
182         addDomainFAB = findViewById(R.id.add_domain_fab)
183
184         // Set the support action bar.
185         setSupportActionBar(toolbar)
186
187         // Get a handle for the action bar.
188         val actionBar = supportActionBar!!
189
190         // Set the back arrow on the action bar.
191         actionBar.setDisplayHomeAsUpEnabled(true)
192
193         // Initialize the database handler.
194         domainsDatabaseHelper = DomainsDatabaseHelper(this)
195
196         // Determine if the device is in two pane mode.  `domain_settings_fragment_container` does not exist on devices with a width less than 900dp.
197         twoPanedMode = (findViewById<View?>(R.id.domain_settings_fragment_container) != null)
198
199         // Configure the add domain floating action button.
200         addDomainFAB.setOnClickListener {
201             // Create an add domain dialog.
202             val addDomainDialog: DialogFragment = addDomain(currentUrl)
203
204             // Show the add domain dialog.
205             addDomainDialog.show(supportFragmentManager, resources.getString(R.string.add_domain))
206         }
207
208         // Get a handle for the activity.
209         val activity: Activity = this
210
211         // Control what the navigation bar back button does.
212         val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
213             override fun handleOnBackPressed() {
214                 if (twoPanedMode) {  // The device is in two-paned mode.
215                     // Save the current domain settings if the domain settings fragment is displayed.
216                     if (findViewById<View?>(R.id.domain_settings_scrollview) != null)
217                         saveDomainSettings(coordinatorLayout)
218
219                     // Dismiss the undo delete snackbar if it is shown.
220                     if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
221                         // Set the close flag.
222                         closeActivityAfterDismissingSnackbar = true
223
224                         // Dismiss the snackbar.
225                         undoDeleteSnackbar!!.dismiss()
226                     } else {
227                         // Go home.
228                         NavUtils.navigateUpFromSameTask(activity)
229                     }
230                 } else if (closeOnBack) {  // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
231                     // Save the current domain settings.
232                     saveDomainSettings(coordinatorLayout)
233
234                     // Go home.
235                     NavUtils.navigateUpFromSameTask(activity)
236                 } else if (findViewById<View?>(R.id.domain_settings_scrollview) != null) {  // The device is in single-paned mode and domain settings fragment is displayed.
237                     // Save the current domain settings.
238                     saveDomainSettings(coordinatorLayout)
239
240                     // Instantiate a new domains list fragment.
241                     val domainsListFragment = DomainsListFragment()
242
243                     // Display the domains list fragment.
244                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
245
246                     // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
247                     populateDomainsListView(-1, domainsListViewPosition)
248
249                     // Show the add domain floating action button.
250                     addDomainFAB.show()
251
252                     // Hide the delete menu item.
253                     deleteMenuItem.isVisible = false
254                 } else {  // The device is in single-paned mode and the domain list fragment is displayed.
255                     // Dismiss the undo delete SnackBar if it is shown.
256                     if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
257                         // Set the close flag.
258                         closeActivityAfterDismissingSnackbar = true
259
260                         // Dismiss the snackbar.
261                         undoDeleteSnackbar!!.dismiss()
262                     } else {
263                         // Go home.
264                         NavUtils.navigateUpFromSameTask(activity)
265                     }
266                 }
267             }
268         }
269
270         // Register the on back pressed callback.
271         onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
272     }
273
274     override fun onCreateOptionsMenu(menu: Menu): Boolean {
275         // Inflate the menu.
276         menuInflater.inflate(R.menu.domains_options_menu, menu)
277
278         // Get a handle for the delete menu item.
279         deleteMenuItem = menu.findItem(R.id.delete_domain)
280
281         // Only display the delete menu item (initially) in two-paned mode.
282         deleteMenuItem.isVisible = twoPanedMode
283
284         // Display the fragments.  This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated.
285         if (appRestarted && domainSettingsDisplayedBeforeRestart) {  // The app was restarted, possibly because the device was rotated, and domain settings were displayed previously.
286             // Reset the app restarted flag.
287             appRestarted = false
288
289             if (twoPanedMode) {  // The device is in two-paned mode.
290                 // Initialize the domains list fragment.
291                 val domainsListFragment = DomainsListFragment()
292
293                 // Display the domains list fragment.
294                 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
295
296                 // Populate the list of domains and highlight the domain that was highlighted before the restart.
297                 populateDomainsListView(domainSettingsDatabaseIdBeforeRestart, domainsListViewPosition)
298             } else {  // The device is in single-paned mode.
299                 // Store the current domain database ID.
300                 currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRestart
301
302                 // Create an arguments bundle.
303                 val argumentsBundle = Bundle()
304
305                 // Add the domain settings arguments.
306                 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
307                 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
308
309                 // Instantiate a new domain settings fragment.
310                 val domainSettingsFragment = DomainSettingsFragment()
311
312                 // Add the arguments bundle to the domain settings fragment.
313                 domainSettingsFragment.arguments = argumentsBundle
314
315                 // Show the delete menu item.
316                 deleteMenuItem.isVisible = true
317
318                 // Hide the add domain floating action button.
319                 addDomainFAB.hide()
320
321                 // Display the domain settings fragment.
322                 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
323             }
324         } else {  // The device was not restarted or, if it was, domain settings were not displayed previously.
325             if (goDirectlyToDatabaseId >= 0) {  // Load the indicated domain settings.
326                 // Store the current domain database ID.
327                 currentDomainDatabaseId = goDirectlyToDatabaseId
328
329                 // Check if the device is in two-paned mode.
330                 if (twoPanedMode) {  // The device is in two-paned mode.
331                     // Instantiate a new domains list fragment.
332                     val domainsListFragment = DomainsListFragment()
333
334                     // Display the domains list fragment.
335                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
336
337                     // Populate the list of domains.
338                     populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition)
339                 } else {  // The device is in single-paned mode.
340                     // Create an arguments bundle.
341                     val argumentsBundle = Bundle()
342
343                     // Add the domain settings to arguments bundle.
344                     argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId)
345                     argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
346
347                     // Instantiate a new domain settings fragment.
348                     val domainSettingsFragment = DomainSettingsFragment()
349
350                     // Add the arguments bundle to the domain settings fragment.
351                     domainSettingsFragment.arguments = argumentsBundle
352
353                     // Show the delete menu item.
354                     deleteMenuItem.isVisible = true
355
356                     // Hide the add domain floating action button.
357                     addDomainFAB.hide()
358
359                     // Display the domain settings fragment.
360                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
361                 }
362             } else {  // Highlight the first domain.
363                 // Instantiate a new domains list fragment.
364                 val domainsListFragment = DomainsListFragment()
365
366                 // Display the domain list fragment.
367                 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
368
369                 // Populate the list of domains.  `-1` highlights the first domain.
370                 populateDomainsListView(-1, domainsListViewPosition)
371             }
372         }
373
374         // Success!
375         return true
376     }
377
378     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
379         // Run the command according to the selected menu item.
380         when (menuItem.itemId) {
381             android.R.id.home -> {  // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
382                 // Check if the device is in two-paned mode.
383                 if (twoPanedMode) {  // The device is in two-paned mode.
384                     // Save the current domain settings if the domain settings fragment is displayed.
385                     if (findViewById<View?>(R.id.domain_settings_scrollview) != null)
386                         saveDomainSettings(coordinatorLayout)
387
388                     // Dismiss the undo delete snackbar if it is shown.
389                     if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
390                         // Set the close flag.
391                         closeActivityAfterDismissingSnackbar = true
392
393                         // Dismiss the snackbar.
394                         undoDeleteSnackbar!!.dismiss()
395                     } else {
396                         // Go home.
397                         NavUtils.navigateUpFromSameTask(this)
398                     }
399                 } else if (closeOnBack) {  // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
400                     // Save the current domain settings.
401                     saveDomainSettings(coordinatorLayout)
402
403                     // Go home.
404                     NavUtils.navigateUpFromSameTask(this)
405                 } else if (findViewById<View?>(R.id.domain_settings_scrollview) != null) {  // The device is in single-paned mode and the domain settings fragment is displayed.
406                     // Save the current domain settings.
407                     saveDomainSettings(coordinatorLayout)
408
409                     // Instantiate a new domains list fragment.
410                     val domainsListFragment = DomainsListFragment()
411
412                     // Display the domains list fragment.
413                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
414
415                     // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
416                     populateDomainsListView(-1, domainsListViewPosition)
417
418                     // Show the add domain floating action button.
419                     addDomainFAB.show()
420
421                     // Hide the delete menu item.
422                     deleteMenuItem.isVisible = false
423                 } else {  // The device is in single-paned mode and domains list fragment is displayed.
424                     // Dismiss the undo delete snackbar if it is shown.
425                     if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
426                         // Set the close flag.
427                         closeActivityAfterDismissingSnackbar = true
428
429                         // Dismiss the snackbar.
430                         undoDeleteSnackbar!!.dismiss()
431                     } else {
432                         // Go home.
433                         NavUtils.navigateUpFromSameTask(this)
434                     }
435                 }
436             }
437
438             R.id.delete_domain -> {  // Delete.
439                 // Get a handle for the activity (used in an inner class below).
440                 val activity: Activity = this
441
442                 // Check to see if the domain settings were loaded directly for editing of this app in single-paned mode.
443                 if (closeOnBack && !twoPanedMode) {  // The activity should delete the domain settings and exit straight to the the main WebView activity.
444                     // Delete the selected domain.
445                     domainsDatabaseHelper.deleteDomain(currentDomainDatabaseId)
446
447                     // Go home.
448                     NavUtils.navigateUpFromSameTask(activity)
449                 } else {  // A snackbar should be shown before deleting the domain settings.
450                     // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists.
451                     closeOnBack = false
452
453                     // Store a copy of the current domain database ID because it could change while the snackbar is displayed.
454                     val databaseIdToDelete = currentDomainDatabaseId
455
456                     // Update the fragments and menu items.
457                     if (twoPanedMode) {  // Two-paned mode.
458                         // Store the deleted domain position, which is needed if undo is selected in the snackbar.
459                         deletedDomainPosition = domainsListView!!.checkedItemPosition
460
461                         // Disable the delete menu item.
462                         deleteMenuItem.isEnabled = false
463
464                         // Remove the domain settings fragment.
465                         supportFragmentManager.beginTransaction().remove(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!!).commitNow()
466                     } else {  // Single-paned mode.
467                         // Instantiate a new domains list fragment.
468                         val domainsListFragment = DomainsListFragment()
469
470                         // Display the domains list fragment.
471                         supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
472
473                         // Show the add domain floating action button.
474                         addDomainFAB.show()
475
476                         // Hide the delete menu item.
477                         deleteMenuItem.isVisible = false
478                     }
479
480                     // Get a cursor that does not show the domain to be deleted.
481                     val domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete)
482
483                     // Setup the domains pending delete cursor adapter.
484                     val domainsPendingDeleteCursorAdapter: CursorAdapter = object : CursorAdapter(this, domainsPendingDeleteCursor, false) {
485                         override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
486                             // Inflate the individual item layout.
487                             return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
488                         }
489
490                         override fun bindView(view: View, context: Context, cursor: Cursor) {
491                             // Get the domain name string.
492                             val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME))
493
494                             // Get a handle for the domain name text view.
495                             val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
496
497                             // Display the domain name.
498                             domainNameTextView.text = domainNameString
499                         }
500                     }
501
502                     // Update the handle for the current domains list view.
503                     domainsListView = findViewById(R.id.domains_listview)
504
505                     // Update the list view.
506                     domainsListView!!.adapter = domainsPendingDeleteCursorAdapter
507
508                     // Display a snackbar.
509                     undoDeleteSnackbar = Snackbar.make(domainsListView!!, R.string.domain_deleted, Snackbar.LENGTH_LONG)
510                         .setAction(R.string.undo) {}  // Do nothing because everything will be handled by `onDismissed()` below.
511                         .addCallback(object : Snackbar.Callback() {
512                             override fun onDismissed(snackbar: Snackbar, event: Int) {
513                                 // Run commands based on the event.
514                                 if (event == DISMISS_EVENT_ACTION) {  // The user pushed the `Undo` button.
515                                     // Create an arguments bundle.
516                                     val argumentsBundle = Bundle()
517
518                                     // Store the domains settings in the arguments bundle.
519                                     argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete)
520                                     argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
521
522                                     // Instantiate a new domain settings fragment.
523                                     val domainSettingsFragment = DomainSettingsFragment()
524
525                                     // Add the arguments bundle to the domain settings fragment.
526                                     domainSettingsFragment.arguments = argumentsBundle
527
528                                     // Display the correct fragments.
529                                     if (twoPanedMode) {  // The device in in two-paned mode.
530                                         // Get a cursor with the current contents of the domains database.
531                                         val undoDeleteDomainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain
532
533                                         // Setup the domains cursor adapter.
534                                         val undoDeleteDomainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, undoDeleteDomainsCursor, false) {
535                                             override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
536                                                 // Inflate the individual item layout.
537                                                 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
538                                             }
539
540                                             override fun bindView(view: View, context: Context, cursor: Cursor) {
541                                                 /// Get the domain name string.
542                                                 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME))
543
544                                                 // Get a handle for the domain name text view.
545                                                 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
546
547                                                 // Display the domain name.
548                                                 domainNameTextView.text = domainNameString
549                                             }
550                                         }
551
552                                         // Update the domains list view.
553                                         domainsListView!!.adapter = undoDeleteDomainsCursorAdapter
554
555                                         // Select the previously deleted domain in the list view.
556                                         domainsListView!!.setItemChecked(deletedDomainPosition, true)
557
558                                         // Display the domain settings fragment.
559                                         supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commitNow()
560
561                                         // Enable the delete menu item.
562                                         deleteMenuItem.isEnabled = true
563                                     } else {  // The device in in one-paned mode.
564                                         // Display the domain settings fragment.
565                                         supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
566
567                                         // Hide the add domain floating action button.
568                                         addDomainFAB.hide()
569
570                                         // Show and enable the delete menu item.
571                                         deleteMenuItem.isVisible = true
572
573                                         // Display the domain settings fragment.
574                                         supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
575                                     }
576                                 } else {  // The snackbar was dismissed without the undo button being pushed.
577                                     // Delete the selected domain.
578                                     domainsDatabaseHelper.deleteDomain(databaseIdToDelete)
579
580                                     // Enable the delete menu item if the system was waiting for a snackbar to be dismissed.
581                                     if (dismissingSnackbar) {
582                                         // Create a runnable to enable the delete menu item.
583                                         val enableDeleteMenuItemRunnable = Runnable {
584                                             // Enable or show the delete menu item according to the display mode.
585                                             if (twoPanedMode)
586                                                 deleteMenuItem.isEnabled = true
587                                             else
588                                                 deleteMenuItem.isVisible = true
589
590                                             // Reset the dismissing snackbar tracker.
591                                             dismissingSnackbar = false
592                                         }
593
594                                         // Instantiate a handler running the main looper.
595                                         val handler = Handler(mainLooper)
596
597                                         // Enable or show the delete menu icon after 100 milliseconds to make sure that the previous domain has been deleted from the database.
598                                         handler.postDelayed(enableDeleteMenuItemRunnable, 100)
599                                     }
600
601                                     // Close the activity if back was pressed.
602                                     if (closeActivityAfterDismissingSnackbar)
603                                         NavUtils.navigateUpFromSameTask(activity)
604                                 }
605                             }
606                         })
607
608                     // Show the Snackbar.
609                     undoDeleteSnackbar!!.show()
610                 }
611             }
612         }
613
614         // Consume the event.
615         return true
616     }
617
618     override fun onSaveInstanceState(savedInstanceState: Bundle) {
619         // Run the default commands.
620         super.onSaveInstanceState(savedInstanceState)
621
622         // Get a handle for the domain settings scrollview.
623         val domainSettingsScrollView = findViewById<ScrollView>(R.id.domain_settings_scrollview)
624
625         // Check to see if the domain settings scrollview exists.
626         if (domainSettingsScrollView == null) {  // The domain settings are not displayed.
627             // Store the domain settings status in the bundle.
628             savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false)
629             savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1)
630             savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0)
631         } else {  // The domain settings are displayed.
632             // Save any changes that have been made to the domain settings.
633             saveDomainSettings(coordinatorLayout)
634
635             // Get the domain settings scroll Y.
636             val domainSettingsScrollY = domainSettingsScrollView.scrollY
637
638             // Store the domain settings status in the bundle.
639             savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true)
640             savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId)
641             savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY)
642         }
643
644         // Check to see if the domains listview exists.
645         if (domainsListView != null) {
646             // Get the domains listview position.
647             val domainsListViewPosition = domainsListView!!.firstVisiblePosition
648
649             // Store the listview position in the bundle.
650             savedInstanceState.putInt(LISTVIEW_POSITION, domainsListViewPosition)
651         }
652     }
653
654     override fun onAddDomain(dialogFragment: DialogFragment) {
655         // Dismiss the undo delete snackbar if it is currently displayed.
656         if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown)
657             undoDeleteSnackbar!!.dismiss()
658
659         // Get a handle for the domain name edit text.
660         val domainNameEditText = dialogFragment.dialog!!.findViewById<EditText>(R.id.domain_name_edittext)
661
662         // Get the domain name string.
663         val domainNameString = domainNameEditText.text.toString()
664
665         // Create the domain and store the database ID in the current domain database ID.
666         currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString)
667
668         // Display the newly created domain.
669         if (twoPanedMode) {  // The device in in two-paned mode.
670             populateDomainsListView(currentDomainDatabaseId, 0)
671         } else {  // The device is in single-paned mode.
672             // Hide the add domain floating action button.
673             addDomainFAB.hide()
674
675             // Show and enable the delete menu item.
676             deleteMenuItem.isVisible = true
677
678             // Create an arguments bundle.
679             val argumentsBundle = Bundle()
680
681             // Add the domain settings to the arguments bundle.  The scroll Y should always be `0` on a new domain.
682             argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
683             argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, 0)
684
685             // Instantiate a new domain settings fragment.
686             val domainSettingsFragment = DomainSettingsFragment()
687
688             // Add the arguments bundle to the domain setting fragment.
689             domainSettingsFragment.arguments = argumentsBundle
690
691             // Display the domain settings fragment.
692             supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
693         }
694     }
695
696     override fun saveDomainSettings(view: View) {
697         // Get handles for the domain settings.
698         val domainNameEditText = view.findViewById<EditText>(R.id.domain_settings_name_edittext)
699         val javaScriptSwitch = view.findViewById<SwitchCompat>(R.id.javascript_switch)
700         val cookiesSwitch = view.findViewById<SwitchCompat>(R.id.cookies_switch)
701         val domStorageSwitch = view.findViewById<SwitchCompat>(R.id.dom_storage_switch)
702         val formDataSwitch = view.findViewById<SwitchCompat>(R.id.form_data_switch) // Form data can be removed once the minimum API >= 26.
703         val easyListSwitch = view.findViewById<SwitchCompat>(R.id.easylist_switch)
704         val easyPrivacySwitch = view.findViewById<SwitchCompat>(R.id.easyprivacy_switch)
705         val fanboysAnnoyanceSwitch = view.findViewById<SwitchCompat>(R.id.fanboys_annoyance_list_switch)
706         val fanboysSocialBlockingSwitch = view.findViewById<SwitchCompat>(R.id.fanboys_social_blocking_list_switch)
707         val ultraListSwitch = view.findViewById<SwitchCompat>(R.id.ultralist_switch)
708         val ultraPrivacySwitch = view.findViewById<SwitchCompat>(R.id.ultraprivacy_switch)
709         val blockAllThirdPartyRequestsSwitch = view.findViewById<SwitchCompat>(R.id.block_all_third_party_requests_switch)
710         val userAgentSpinner = view.findViewById<Spinner>(R.id.user_agent_spinner)
711         val customUserAgentEditText = view.findViewById<EditText>(R.id.custom_user_agent_edittext)
712         val xRequestedWithHeaderSpinner = view.findViewById<Spinner>(R.id.x_requested_with_header_spinner)
713         val fontSizeSpinner = view.findViewById<Spinner>(R.id.font_size_spinner)
714         val customFontSizeEditText = view.findViewById<EditText>(R.id.custom_font_size_edittext)
715         val swipeToRefreshSpinner = view.findViewById<Spinner>(R.id.swipe_to_refresh_spinner)
716         val webViewThemeSpinner = view.findViewById<Spinner>(R.id.webview_theme_spinner)
717         val wideViewportSpinner = view.findViewById<Spinner>(R.id.wide_viewport_spinner)
718         val displayWebpageImagesSpinner = view.findViewById<Spinner>(R.id.display_webpage_images_spinner)
719         val pinnedSslCertificateSwitch = view.findViewById<SwitchCompat>(R.id.pinned_ssl_certificate_switch)
720         val currentWebsiteCertificateRadioButton = view.findViewById<RadioButton>(R.id.current_website_certificate_radiobutton)
721         val pinnedIpAddressesSwitch = view.findViewById<SwitchCompat>(R.id.pinned_ip_addresses_switch)
722         val currentIpAddressesRadioButton = view.findViewById<RadioButton>(R.id.current_ip_addresses_radiobutton)
723
724         // Extract the data for the domain settings.
725         val domainNameString = domainNameEditText.text.toString()
726         val javaScript = javaScriptSwitch.isChecked
727         val cookies = cookiesSwitch.isChecked
728         val domStorage = domStorageSwitch.isChecked
729         val formData = formDataSwitch.isChecked // Form data can be removed once the minimum API >= 26.
730         val easyList = easyListSwitch.isChecked
731         val easyPrivacy = easyPrivacySwitch.isChecked
732         val fanboysAnnoyance = fanboysAnnoyanceSwitch.isChecked
733         val fanboysSocialBlocking = fanboysSocialBlockingSwitch.isChecked
734         val ultraList = ultraListSwitch.isChecked
735         val ultraPrivacy = ultraPrivacySwitch.isChecked
736         val blockAllThirdPartyRequests = blockAllThirdPartyRequestsSwitch.isChecked
737         val userAgentSwitchPosition = userAgentSpinner.selectedItemPosition
738         val xRequestedWithHeaderSwitchInt = xRequestedWithHeaderSpinner.selectedItemPosition
739         val fontSizeSwitchPosition = fontSizeSpinner.selectedItemPosition
740         val swipeToRefreshInt = swipeToRefreshSpinner.selectedItemPosition
741         val webViewThemeInt = webViewThemeSpinner.selectedItemPosition
742         val wideViewportInt = wideViewportSpinner.selectedItemPosition
743         val displayWebpageImagesInt = displayWebpageImagesSpinner.selectedItemPosition
744         val pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked
745         val pinnedIpAddress = pinnedIpAddressesSwitch.isChecked
746
747         // Get the user agent name.
748         val userAgentName: String = when (userAgentSwitchPosition) {
749             MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT -> resources.getString(R.string.system_default_user_agent)  // Set the user agent name to be `System default user agent`.
750             MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT -> customUserAgentEditText.text.toString()  // Set the user agent name to be the custom user agent.
751             else -> {
752                 // Get the array of user agent names.
753                 val userAgentNameArray = resources.getStringArray(R.array.user_agent_names)
754
755                 // Set the user agent name from the array.  The domain spinner has one more entry than the name array, so the position must be decremented.
756                 userAgentNameArray[userAgentSwitchPosition - 1]
757             }
758         }
759
760         // Initialize the font size integer.  `0` indicates the system default font size.
761         var fontSizeInt = 0
762
763         // Use a custom font size if it is selected.
764         if (fontSizeSwitchPosition == 1)
765             fontSizeInt = customFontSizeEditText.text.toString().toInt()
766
767         // Save the domain settings.
768         domainsDatabaseHelper.updateDomain(currentDomainDatabaseId, domainNameString, javaScript, cookies, domStorage, formData, easyList, easyPrivacy, fanboysAnnoyance, fanboysSocialBlocking, ultraList,
769             ultraPrivacy, blockAllThirdPartyRequests, userAgentName, xRequestedWithHeaderSwitchInt, fontSizeInt, swipeToRefreshInt, webViewThemeInt, wideViewportInt, displayWebpageImagesInt,
770             pinnedSslCertificate, pinnedIpAddress)
771
772         // Update the pinned SSL certificate if a new one is checked.
773         if (currentWebsiteCertificateRadioButton.isChecked)
774             domainsDatabaseHelper.updatePinnedSslCertificate(currentDomainDatabaseId, sslIssuedToCName!!, sslIssuedToOName!!, sslIssuedToUName!!, sslIssuedByCName!!, sslIssuedByOName!!, sslIssuedByUName!!,
775                 sslStartDateLong, sslEndDateLong)
776
777         // Update the pinned IP addresses if new ones are checked.
778         if (currentIpAddressesRadioButton.isChecked)
779             domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses!!)
780     }
781
782     private fun populateDomainsListView(highlightedDomainDatabaseId: Int, domainsListViewPosition: Int) {
783         // Get a cursor with the current contents of the domains database.
784         val domainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain
785
786         // Setup the domains cursor adapter.
787         val domainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, domainsCursor, false) {
788             override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
789                 // Inflate the individual item layout.
790                 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
791             }
792
793             override fun bindView(view: View, context: Context, cursor: Cursor) {
794                 // Get a handle for the domain name text view.
795                 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
796
797                 // Get the domain name string.
798                 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME))
799
800                 // Set the domain name.
801                 domainNameTextView.text = domainNameString
802             }
803         }
804
805         // get a handle for the current domains listview.
806         domainsListView = findViewById(R.id.domains_listview)
807
808         // Update the list view.
809         domainsListView!!.adapter = domainsCursorAdapter
810
811         // Restore the scroll position.
812         domainsListView!!.setSelection(domainsListViewPosition)
813
814         // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain.
815         if (twoPanedMode && domainsCursor.count > 0) {  // Two-paned mode is enabled and there is at least one domain.
816             // Initialize the highlighted domain position tracker.
817             var highlightedDomainPosition = 0
818
819             // Get the cursor position for the highlighted domain.
820             for (i in 0 until domainsCursor.count) {
821                 // Move to position `i` in the cursor.
822                 domainsCursor.moveToPosition(i)
823
824                 // Get the database ID for this position.
825                 val currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID))
826
827                 // Set the highlighted domain position if the database ID for this matches the highlighted domain database ID.
828                 if (highlightedDomainDatabaseId == currentDatabaseId)
829                     highlightedDomainPosition = i
830             }
831
832             // Select the highlighted domain.
833             domainsListView!!.setItemChecked(highlightedDomainPosition, true)
834
835             // Move to the highlighted domain.
836             domainsCursor.moveToPosition(highlightedDomainPosition)
837
838             // Get the database ID for the highlighted domain.
839             currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID))
840
841             // Create an arguments bundle.
842             val argumentsBundle = Bundle()
843
844             // Store the domain settings in the arguments bundle.
845             argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
846             argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
847
848             // Instantiate a new domain settings fragment.
849             val domainSettingsFragment = DomainSettingsFragment()
850
851             // Add the arguments bundle to the domain settings fragment.
852             domainSettingsFragment.arguments = argumentsBundle
853
854             // Display the domain settings fragment.
855             supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit()
856
857             // Enable the delete options menu items.
858             deleteMenuItem.isEnabled = true
859         } else if (twoPanedMode) {  // Two-paned mode is enabled but there are no domains.
860             // Disable the delete menu item.
861             deleteMenuItem.isEnabled = false
862         }
863     }
864
865     override fun dismissSnackbar() {
866         // Dismiss the undo delete snackbar if it is shown.
867         if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
868             // Dismiss the snackbar.
869             undoDeleteSnackbar!!.dismiss()
870         }
871     }
872
873     public override fun onDestroy() {
874         // Close the domains database helper.
875         domainsDatabaseHelper.close()
876
877         // Run the default commands.
878         super.onDestroy()
879     }
880 }