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