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