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