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