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