2 * Copyright 2017-2023 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
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.
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.
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/>.
20 package com.stoutner.privacybrowser.activities
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
39 import androidx.activity.OnBackPressedCallback
40 import androidx.appcompat.app.AppCompatActivity
41 import androidx.appcompat.widget.SwitchCompat
42 import androidx.appcompat.widget.Toolbar
43 import androidx.core.app.NavUtils
44 import androidx.cursoradapter.widget.CursorAdapter
45 import androidx.fragment.app.DialogFragment
46 import androidx.preference.PreferenceManager
48 import com.google.android.material.floatingactionbutton.FloatingActionButton
49 import com.google.android.material.snackbar.Snackbar
51 import com.stoutner.privacybrowser.R
52 import com.stoutner.privacybrowser.dialogs.AddDomainDialog.AddDomainListener
53 import com.stoutner.privacybrowser.dialogs.AddDomainDialog.Companion.addDomain
54 import com.stoutner.privacybrowser.fragments.DomainSettingsFragment
55 import com.stoutner.privacybrowser.fragments.DomainsListFragment
56 import com.stoutner.privacybrowser.fragments.DomainsListFragment.DismissSnackbarInterface
57 import com.stoutner.privacybrowser.fragments.DomainsListFragment.SaveDomainSettingsInterface
58 import com.stoutner.privacybrowser.helpers.DOMAIN_NAME
59 import com.stoutner.privacybrowser.helpers.ID
60 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
62 // Define the public constants.
63 const val CLOSE_ON_BACK = "close_on_back"
64 const val CURRENT_IP_ADDRESSES = "current_ip_addresses"
65 const val LOAD_DOMAIN = "load_domain"
66 const val SSL_END_DATE = "ssl_end_date"
67 const val SSL_ISSUED_BY_CNAME = "ssl_issued_by_cname"
68 const val SSL_ISSUED_BY_ONAME = "ssl_issued_by_oname"
69 const val SSL_ISSUED_BY_UNAME = "ssl_issued_by_uname"
70 const val SSL_ISSUED_TO_CNAME = "ssl_issued_to_cname"
71 const val SSL_ISSUED_TO_ONAME = "ssl_issued_to_oname"
72 const val SSL_ISSUED_TO_UNAME = "ssl_issued_to_uname"
73 const val SSL_START_DATE = "ssl_start_date"
75 // Define the class constants.
76 private const val DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_id"
77 private const val DOMAIN_SETTINGS_DISPLAYED = "domain_settings_displayed"
78 private const val DOMAIN_SETTINGS_SCROLL_Y = "domain_settings_scroll_y"
79 private const val LISTVIEW_POSITION = "listview_position"
81 class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarInterface, SaveDomainSettingsInterface {
83 // Define the public variables.
84 var currentDomainDatabaseId = 0 // Used in `DomainsListFragment`.
85 var dismissingSnackbar = false // Used in `DomainsListFragment`.
86 var domainsListViewPosition = 0 // Used in `DomainsListFragment`.
87 var sslEndDateLong: Long = 0 // Used in `DomainsSettingsFragment`.
88 var sslStartDateLong: Long = 0 // Used in `DomainSettingsFragment`.
89 var twoPanedMode = false // Used in `DomainsListFragment`.
91 // Declare the public views. They are used in `DomainsListFragment`.
92 lateinit var deleteMenuItem: MenuItem
94 // Declare the SSL certificate and IP address strings.
95 var currentIpAddresses: String? = null // Used in `DomainSettingsFragment`.
96 var sslIssuedToCName: String? = null // Used in `DomainSettingsFragment`.
97 var sslIssuedToOName: String? = null // Used in `DomainSettingsFragment`.
98 var sslIssuedToUName: String? = null // Used in `DomainSettingsFragment`.
99 var sslIssuedByCName: String? = null // Used in `DomainSettingsFragment`.
100 var sslIssuedByOName: String? = null // Used in `DomainSettingsFragment`.
101 var sslIssuedByUName: String? = null // Used in `DomainSettingsFragment`.
104 // Declare the class views.
105 private lateinit var addDomainFAB: FloatingActionButton
106 private lateinit var coordinatorLayout: View
107 private var domainsListView: ListView? = null
108 private var undoDeleteSnackbar: Snackbar? = null
110 // Declare the class variables.
111 private lateinit var domainsDatabaseHelper: DomainsDatabaseHelper
113 // Define the class variables.
114 private var appRestarted = false
115 private var closeActivityAfterDismissingSnackbar = false
116 private var closeOnBack = false
117 private var deletedDomainPosition = 0
118 private var domainSettingsDatabaseIdBeforeRestart = 0
119 private var domainSettingsDisplayedBeforeRestart = false
120 private var domainSettingsScrollY = 0
121 private var goDirectlyToDatabaseId = -1
123 override fun onCreate(savedInstanceState: Bundle?) {
124 // Get a handle for the shared preferences.
125 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
127 // Get the preferences.
128 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
129 val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
131 // Disable screenshots if not allowed.
132 if (!allowScreenshots) {
133 window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
136 // Run the default commands.
137 super.onCreate(savedInstanceState)
139 // Process the saved instance state if it is not null.
140 if (savedInstanceState != null) {
141 // Extract the values from the saved instance state if it is not null.
142 domainsListViewPosition = savedInstanceState.getInt(LISTVIEW_POSITION)
143 domainSettingsDisplayedBeforeRestart = savedInstanceState.getBoolean(DOMAIN_SETTINGS_DISPLAYED)
144 domainSettingsDatabaseIdBeforeRestart = savedInstanceState.getInt(DOMAIN_SETTINGS_DATABASE_ID)
145 domainSettingsScrollY = savedInstanceState.getInt(DOMAIN_SETTINGS_SCROLL_Y)
147 // Set the app restarted flag.
151 // Get the launching intent
154 // Extract the domain to load if there is one. `-1` is the default value.
155 goDirectlyToDatabaseId = intent.getIntExtra(LOAD_DOMAIN, -1)
157 // Get the status of close-on-back, which is true when the domains activity is called from the options menu.
158 closeOnBack = intent.getBooleanExtra(CLOSE_ON_BACK, false)
160 // Get the current URL.
161 val currentUrl = intent.getStringExtra(CURRENT_URL)
163 // Store the current SSL certificate information in class variables.
164 sslIssuedToCName = intent.getStringExtra(SSL_ISSUED_TO_CNAME)
165 sslIssuedToOName = intent.getStringExtra(SSL_ISSUED_TO_ONAME)
166 sslIssuedToUName = intent.getStringExtra(SSL_ISSUED_TO_UNAME)
167 sslIssuedByCName = intent.getStringExtra(SSL_ISSUED_BY_CNAME)
168 sslIssuedByOName = intent.getStringExtra(SSL_ISSUED_BY_ONAME)
169 sslIssuedByUName = intent.getStringExtra(SSL_ISSUED_BY_UNAME)
170 sslStartDateLong = intent.getLongExtra(SSL_START_DATE, 0)
171 sslEndDateLong = intent.getLongExtra(SSL_END_DATE, 0)
172 currentIpAddresses = intent.getStringExtra(CURRENT_IP_ADDRESSES)
174 // Set the content view.
176 setContentView(R.layout.domains_bottom_appbar)
178 setContentView(R.layout.domains_top_appbar)
180 // Get handles for the views.
181 coordinatorLayout = findViewById(R.id.domains_coordinatorlayout)
182 val toolbar = findViewById<Toolbar>(R.id.domains_toolbar)
183 addDomainFAB = findViewById(R.id.add_domain_fab)
185 // Set the support action bar.
186 setSupportActionBar(toolbar)
188 // Get a handle for the action bar.
189 val actionBar = supportActionBar!!
191 // Set the back arrow on the action bar.
192 actionBar.setDisplayHomeAsUpEnabled(true)
194 // Initialize the database handler.
195 domainsDatabaseHelper = DomainsDatabaseHelper(this)
197 // Determine if the device is in two pane mode. `domain_settings_fragment_container` does not exist on devices with a width less than 900dp.
198 twoPanedMode = (findViewById<View?>(R.id.domain_settings_fragment_container) != null)
200 // Configure the add domain floating action button.
201 addDomainFAB.setOnClickListener {
202 // Create an add domain dialog.
203 val addDomainDialog: DialogFragment = addDomain(currentUrl)
205 // Show the add domain dialog.
206 addDomainDialog.show(supportFragmentManager, resources.getString(R.string.add_domain))
209 // Control what the system back command does.
210 val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
211 override fun handleOnBackPressed() {
212 if (twoPanedMode) { // The device is in two-paned mode.
213 // Save the current domain settings if the domain settings fragment is displayed.
214 if (findViewById<View?>(R.id.domain_settings_scrollview) != null)
215 saveDomainSettings(coordinatorLayout)
217 // Dismiss the undo delete snackbar if it is shown.
218 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
219 // Set the close flag.
220 closeActivityAfterDismissingSnackbar = true
222 // Dismiss the snackbar.
223 undoDeleteSnackbar!!.dismiss()
228 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
229 // Save the current domain settings.
230 saveDomainSettings(coordinatorLayout)
234 } else if (findViewById<View?>(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and domain settings fragment is displayed.
235 // Save the current domain settings.
236 saveDomainSettings(coordinatorLayout)
238 // Instantiate a new domains list fragment.
239 val domainsListFragment = DomainsListFragment()
241 // Display the domains list fragment.
242 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
244 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
245 populateDomainsListView(-1, domainsListViewPosition)
247 // Show the add domain floating action button.
250 // Hide the delete menu item.
251 deleteMenuItem.isVisible = false
252 } else { // The device is in single-paned mode and the domain list fragment is displayed.
253 // Dismiss the undo delete SnackBar if it is shown.
254 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
255 // Set the close flag.
256 closeActivityAfterDismissingSnackbar = true
258 // Dismiss the snackbar.
259 undoDeleteSnackbar!!.dismiss()
268 // Register the on back pressed callback.
269 onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
272 override fun onCreateOptionsMenu(menu: Menu): Boolean {
274 menuInflater.inflate(R.menu.domains_options_menu, menu)
276 // Get a handle for the delete menu item.
277 deleteMenuItem = menu.findItem(R.id.delete_domain)
279 // Only display the delete menu item (initially) in two-paned mode.
280 deleteMenuItem.isVisible = twoPanedMode
282 // Display the fragments. This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated.
283 if (appRestarted && domainSettingsDisplayedBeforeRestart) { // The app was restarted, possibly because the device was rotated, and domain settings were displayed previously.
284 // Reset the app restarted flag.
287 if (twoPanedMode) { // The device is in two-paned mode.
288 // Initialize the domains list fragment.
289 val domainsListFragment = DomainsListFragment()
291 // Display the domains list fragment.
292 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
294 // Populate the list of domains and highlight the domain that was highlighted before the restart.
295 populateDomainsListView(domainSettingsDatabaseIdBeforeRestart, domainsListViewPosition)
296 } else { // The device is in single-paned mode.
297 // Store the current domain database ID.
298 currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRestart
300 // Create an arguments bundle.
301 val argumentsBundle = Bundle()
303 // Add the domain settings arguments.
304 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
305 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
307 // Instantiate a new domain settings fragment.
308 val domainSettingsFragment = DomainSettingsFragment()
310 // Add the arguments bundle to the domain settings fragment.
311 domainSettingsFragment.arguments = argumentsBundle
313 // Show the delete menu item.
314 deleteMenuItem.isVisible = true
316 // Hide the add domain floating action button.
319 // Display the domain settings fragment.
320 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
322 } else { // The device was not restarted or, if it was, domain settings were not displayed previously.
323 if (goDirectlyToDatabaseId >= 0) { // Load the indicated domain settings.
324 // Store the current domain database ID.
325 currentDomainDatabaseId = goDirectlyToDatabaseId
327 // Check if the device is in two-paned mode.
328 if (twoPanedMode) { // The device is in two-paned mode.
329 // Instantiate a new domains list fragment.
330 val domainsListFragment = DomainsListFragment()
332 // Display the domains list fragment.
333 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
335 // Populate the list of domains.
336 populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition)
337 } else { // The device is in single-paned mode.
338 // Create an arguments bundle.
339 val argumentsBundle = Bundle()
341 // Add the domain settings to arguments bundle.
342 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId)
343 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
345 // Instantiate a new domain settings fragment.
346 val domainSettingsFragment = DomainSettingsFragment()
348 // Add the arguments bundle to the domain settings fragment.
349 domainSettingsFragment.arguments = argumentsBundle
351 // Show the delete menu item.
352 deleteMenuItem.isVisible = true
354 // Hide the add domain floating action button.
357 // Display the domain settings fragment.
358 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
360 } else { // Highlight the first domain.
361 // Instantiate a new domains list fragment.
362 val domainsListFragment = DomainsListFragment()
364 // Display the domain list fragment.
365 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
367 // Populate the list of domains. `-1` highlights the first domain.
368 populateDomainsListView(-1, domainsListViewPosition)
376 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
377 // Run the command according to the selected menu item.
378 when (menuItem.itemId) {
379 android.R.id.home -> { // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
380 // Check if the device is in two-paned mode.
381 if (twoPanedMode) { // The device is in two-paned mode.
382 // Save the current domain settings if the domain settings fragment is displayed.
383 if (findViewById<View?>(R.id.domain_settings_scrollview) != null)
384 saveDomainSettings(coordinatorLayout)
386 // Dismiss the undo delete snackbar if it is shown.
387 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
388 // Set the close flag.
389 closeActivityAfterDismissingSnackbar = true
391 // Dismiss the snackbar.
392 undoDeleteSnackbar!!.dismiss()
397 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
398 // Save the current domain settings.
399 saveDomainSettings(coordinatorLayout)
403 } else if (findViewById<View?>(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed.
404 // Save the current domain settings.
405 saveDomainSettings(coordinatorLayout)
407 // Instantiate a new domains list fragment.
408 val domainsListFragment = DomainsListFragment()
410 // Display the domains list fragment.
411 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
413 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
414 populateDomainsListView(-1, domainsListViewPosition)
416 // Show the add domain floating action button.
419 // Hide the delete menu item.
420 deleteMenuItem.isVisible = false
421 } else { // The device is in single-paned mode and domains list fragment is displayed.
422 // Dismiss the undo delete snackbar if it is shown.
423 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
424 // Set the close flag.
425 closeActivityAfterDismissingSnackbar = true
427 // Dismiss the snackbar.
428 undoDeleteSnackbar!!.dismiss()
436 R.id.delete_domain -> { // Delete.
437 // Get a handle for the activity (used in an inner class below).
438 val activity: Activity = this
440 // Check to see if the domain settings were loaded directly for editing of this app in single-paned mode.
441 if (closeOnBack && !twoPanedMode) { // The activity should delete the domain settings and exit straight to the the main WebView activity.
442 // Delete the selected domain.
443 domainsDatabaseHelper.deleteDomain(currentDomainDatabaseId)
446 NavUtils.navigateUpFromSameTask(activity)
447 } else { // A snackbar should be shown before deleting the domain settings.
448 // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists.
451 // Store a copy of the current domain database ID because it could change while the snackbar is displayed.
452 val databaseIdToDelete = currentDomainDatabaseId
454 // Update the fragments and menu items.
455 if (twoPanedMode) { // Two-paned mode.
456 // Store the deleted domain position, which is needed if undo is selected in the snackbar.
457 deletedDomainPosition = domainsListView!!.checkedItemPosition
459 // Disable the delete menu item.
460 deleteMenuItem.isEnabled = false
462 // Remove the domain settings fragment.
463 supportFragmentManager.beginTransaction().remove(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!!).commitNow()
464 } else { // Single-paned mode.
465 // Instantiate a new domains list fragment.
466 val domainsListFragment = DomainsListFragment()
468 // Display the domains list fragment.
469 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
471 // Show the add domain floating action button.
474 // Hide the delete menu item.
475 deleteMenuItem.isVisible = false
478 // Get a cursor that does not show the domain to be deleted.
479 val domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete)
481 // Setup the domains pending delete cursor adapter.
482 val domainsPendingDeleteCursorAdapter: CursorAdapter = object : CursorAdapter(this, domainsPendingDeleteCursor, false) {
483 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
484 // Inflate the individual item layout.
485 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
488 override fun bindView(view: View, context: Context, cursor: Cursor) {
489 // Get the domain name string.
490 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME))
492 // Get a handle for the domain name text view.
493 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
495 // Display the domain name.
496 domainNameTextView.text = domainNameString
500 // Update the handle for the current domains list view.
501 domainsListView = findViewById(R.id.domains_listview)
503 // Update the list view.
504 domainsListView!!.adapter = domainsPendingDeleteCursorAdapter
506 // Display a snackbar.
507 undoDeleteSnackbar = Snackbar.make(domainsListView!!, R.string.domain_deleted, Snackbar.LENGTH_LONG)
508 .setAction(R.string.undo) {} // Do nothing because everything will be handled by `onDismissed()` below.
509 .addCallback(object : Snackbar.Callback() {
510 override fun onDismissed(snackbar: Snackbar, event: Int) {
511 // Run commands based on the event.
512 if (event == DISMISS_EVENT_ACTION) { // The user pushed the `Undo` button.
513 // Create an arguments bundle.
514 val argumentsBundle = Bundle()
516 // Store the domains settings in the arguments bundle.
517 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete)
518 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
520 // Instantiate a new domain settings fragment.
521 val domainSettingsFragment = DomainSettingsFragment()
523 // Add the arguments bundle to the domain settings fragment.
524 domainSettingsFragment.arguments = argumentsBundle
526 // Display the correct fragments.
527 if (twoPanedMode) { // The device in in two-paned mode.
528 // Get a cursor with the current contents of the domains database.
529 val undoDeleteDomainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain
531 // Setup the domains cursor adapter.
532 val undoDeleteDomainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, undoDeleteDomainsCursor, false) {
533 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
534 // Inflate the individual item layout.
535 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
538 override fun bindView(view: View, context: Context, cursor: Cursor) {
539 /// Get the domain name string.
540 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME))
542 // Get a handle for the domain name text view.
543 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
545 // Display the domain name.
546 domainNameTextView.text = domainNameString
550 // Update the domains list view.
551 domainsListView!!.adapter = undoDeleteDomainsCursorAdapter
553 // Select the previously deleted domain in the list view.
554 domainsListView!!.setItemChecked(deletedDomainPosition, true)
556 // Display the domain settings fragment.
557 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commitNow()
559 // Enable the delete menu item.
560 deleteMenuItem.isEnabled = true
561 } else { // The device in in one-paned mode.
562 // Display the domain settings fragment.
563 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
565 // Hide the add domain floating action button.
568 // Show and enable the delete menu item.
569 deleteMenuItem.isVisible = true
571 // Display the domain settings fragment.
572 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
574 } else { // The snackbar was dismissed without the undo button being pushed.
575 // Delete the selected domain.
576 domainsDatabaseHelper.deleteDomain(databaseIdToDelete)
578 // Enable the delete menu item if the system was waiting for a snackbar to be dismissed.
579 if (dismissingSnackbar) {
580 // Create a runnable to enable the delete menu item.
581 val enableDeleteMenuItemRunnable = Runnable {
582 // Enable or show the delete menu item according to the display mode.
584 deleteMenuItem.isEnabled = true
586 deleteMenuItem.isVisible = true
588 // Reset the dismissing snackbar tracker.
589 dismissingSnackbar = false
592 // Instantiate a handler running the main looper.
593 val handler = Handler(mainLooper)
595 // Enable or show the delete menu icon after 100 milliseconds to make sure that the previous domain has been deleted from the database.
596 handler.postDelayed(enableDeleteMenuItemRunnable, 100)
599 // Close the activity if back was pressed.
600 if (closeActivityAfterDismissingSnackbar)
601 NavUtils.navigateUpFromSameTask(activity)
606 // Show the Snackbar.
607 undoDeleteSnackbar!!.show()
612 // Consume the event.
616 override fun onSaveInstanceState(savedInstanceState: Bundle) {
617 // Run the default commands.
618 super.onSaveInstanceState(savedInstanceState)
620 // Get a handle for the domain settings scrollview.
621 val domainSettingsScrollView = findViewById<ScrollView>(R.id.domain_settings_scrollview)
623 // Check to see if the domain settings scrollview exists.
624 if (domainSettingsScrollView == null) { // The domain settings are not displayed.
625 // Store the domain settings status in the bundle.
626 savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false)
627 savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1)
628 savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0)
629 } else { // The domain settings are displayed.
630 // Save any changes that have been made to the domain settings.
631 saveDomainSettings(coordinatorLayout)
633 // Get the domain settings scroll Y.
634 val domainSettingsScrollY = domainSettingsScrollView.scrollY
636 // Store the domain settings status in the bundle.
637 savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true)
638 savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId)
639 savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY)
642 // Check to see if the domains listview exists.
643 if (domainsListView != null) {
644 // Get the domains listview position.
645 val domainsListViewPosition = domainsListView!!.firstVisiblePosition
647 // Store the listview position in the bundle.
648 savedInstanceState.putInt(LISTVIEW_POSITION, domainsListViewPosition)
652 override fun addDomain(dialogFragment: DialogFragment) {
653 // Dismiss the undo delete snackbar if it is currently displayed.
654 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown)
655 undoDeleteSnackbar!!.dismiss()
657 // Get a handle for the domain name edit text.
658 val domainNameEditText = dialogFragment.dialog!!.findViewById<EditText>(R.id.domain_name_edittext)
660 // Get the domain name string.
661 val domainNameString = domainNameEditText.text.toString()
663 // Create the domain and store the database ID in the current domain database ID.
664 currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString)
666 // Display the newly created domain.
667 if (twoPanedMode) { // The device in in two-paned mode.
668 populateDomainsListView(currentDomainDatabaseId, 0)
669 } else { // The device is in single-paned mode.
670 // Hide the add domain floating action button.
673 // Show and enable the delete menu item.
674 deleteMenuItem.isVisible = true
676 // Create an arguments bundle.
677 val argumentsBundle = Bundle()
679 // Add the domain settings to the arguments bundle. The scroll Y should always be `0` on a new domain.
680 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
681 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, 0)
683 // Instantiate a new domain settings fragment.
684 val domainSettingsFragment = DomainSettingsFragment()
686 // Add the arguments bundle to the domain setting fragment.
687 domainSettingsFragment.arguments = argumentsBundle
689 // Display the domain settings fragment.
690 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
694 override fun saveDomainSettings(view: View) {
695 // Get handles for the domain settings.
696 val domainNameEditText = view.findViewById<EditText>(R.id.domain_settings_name_edittext)
697 val javaScriptSpinner = view.findViewById<Spinner>(R.id.javascript_spinner)
698 val cookiesSpinner = view.findViewById<Spinner>(R.id.cookies_spinner)
699 val domStorageSpinner = view.findViewById<Spinner>(R.id.dom_storage_spinner)
700 val formDataSpinner = view.findViewById<Spinner>(R.id.form_data_spinner) // Form data can be removed once the minimum API >= 26.
701 val userAgentSpinner = view.findViewById<Spinner>(R.id.user_agent_spinner)
702 val customUserAgentEditText = view.findViewById<EditText>(R.id.custom_user_agent_edittext)
703 val easyListSpinner = view.findViewById<Spinner>(R.id.easylist_spinner)
704 val easyPrivacySpinner = view.findViewById<Spinner>(R.id.easyprivacy_spinner)
705 val fanboysAnnoyanceSpinner = view.findViewById<Spinner>(R.id.fanboys_annoyance_list_spinner)
706 val fanboysSocialBlockingSpinner = view.findViewById<Spinner>(R.id.fanboys_social_blocking_list_spinner)
707 val ultraListSpinner = view.findViewById<Spinner>(R.id.ultralist_spinner)
708 val ultraPrivacySpinner = view.findViewById<Spinner>(R.id.ultraprivacy_spinner)
709 val blockAllThirdPartyRequestsSpinner = view.findViewById<Spinner>(R.id.block_all_third_party_requests_spinner)
710 val fontSizeSpinner = view.findViewById<Spinner>(R.id.font_size_spinner)
711 val customFontSizeEditText = view.findViewById<EditText>(R.id.custom_font_size_edittext)
712 val swipeToRefreshSpinner = view.findViewById<Spinner>(R.id.swipe_to_refresh_spinner)
713 val webViewThemeSpinner = view.findViewById<Spinner>(R.id.webview_theme_spinner)
714 val wideViewportSpinner = view.findViewById<Spinner>(R.id.wide_viewport_spinner)
715 val displayWebpageImagesSpinner = view.findViewById<Spinner>(R.id.display_images_spinner)
716 val pinnedSslCertificateSwitch = view.findViewById<SwitchCompat>(R.id.pinned_ssl_certificate_switch)
717 val currentWebsiteCertificateRadioButton = view.findViewById<RadioButton>(R.id.current_website_certificate_radiobutton)
718 val pinnedIpAddressesSwitch = view.findViewById<SwitchCompat>(R.id.pinned_ip_addresses_switch)
719 val currentIpAddressesRadioButton = view.findViewById<RadioButton>(R.id.current_ip_addresses_radiobutton)
721 // Extract the data for the domain settings.
722 val domainNameString = domainNameEditText.text.toString()
723 val javaScriptInt = javaScriptSpinner.selectedItemPosition
724 val cookiesInt = cookiesSpinner.selectedItemPosition
725 val domStorageInt = domStorageSpinner.selectedItemPosition
726 val formDataInt = formDataSpinner.selectedItemPosition // Form data can be removed once the minimum API >= 26.
727 val userAgentSwitchPosition = userAgentSpinner.selectedItemPosition
728 val easyListInt = easyListSpinner.selectedItemPosition
729 val easyPrivacyInt = easyPrivacySpinner.selectedItemPosition
730 val fanboysAnnoyanceInt = fanboysAnnoyanceSpinner.selectedItemPosition
731 val fanboysSocialBlockingInt = fanboysSocialBlockingSpinner.selectedItemPosition
732 val ultraListInt = ultraListSpinner.selectedItemPosition
733 val ultraPrivacyInt = ultraPrivacySpinner.selectedItemPosition
734 val blockAllThirdPartyRequestsInt = blockAllThirdPartyRequestsSpinner.selectedItemPosition
735 val fontSizeSwitchPosition = fontSizeSpinner.selectedItemPosition
736 val swipeToRefreshInt = swipeToRefreshSpinner.selectedItemPosition
737 val webViewThemeInt = webViewThemeSpinner.selectedItemPosition
738 val wideViewportInt = wideViewportSpinner.selectedItemPosition
739 val displayWebpageImagesInt = displayWebpageImagesSpinner.selectedItemPosition
740 val pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked
741 val pinnedIpAddress = pinnedIpAddressesSwitch.isChecked
743 // Get the user agent name.
744 val userAgentName: String = when (userAgentSwitchPosition) {
745 // Set the user agent name to be `System default user agent`.
746 DOMAINS_SYSTEM_DEFAULT_USER_AGENT -> resources.getString(R.string.system_default_user_agent)
748 // Set the user agent name to be the custom user agent.
749 DOMAINS_CUSTOM_USER_AGENT -> customUserAgentEditText.text.toString()
752 // Get the array of user agent names.
753 val userAgentNameArray = resources.getStringArray(R.array.user_agent_names)
755 // Set the user agent name from the array. The domain spinner has one more entry than the name array, so the position must be decremented.
756 userAgentNameArray[userAgentSwitchPosition - 1]
760 // Initialize the font size integer. `0` indicates the system default font size.
763 // Use a custom font size if it is selected.
764 if (fontSizeSwitchPosition == 1)
765 fontSizeInt = customFontSizeEditText.text.toString().toInt()
767 // Save the domain settings.
768 domainsDatabaseHelper.updateDomain(currentDomainDatabaseId, domainNameString, javaScriptInt, cookiesInt, domStorageInt, formDataInt, userAgentName, easyListInt, easyPrivacyInt, fanboysAnnoyanceInt,
769 fanboysSocialBlockingInt, ultraListInt, ultraPrivacyInt, blockAllThirdPartyRequestsInt, fontSizeInt, swipeToRefreshInt, webViewThemeInt, wideViewportInt, displayWebpageImagesInt,
770 pinnedSslCertificate, pinnedIpAddress)
772 // Update the pinned SSL certificate if a new one is checked.
773 if (currentWebsiteCertificateRadioButton.isChecked)
774 domainsDatabaseHelper.updatePinnedSslCertificate(currentDomainDatabaseId, sslIssuedToCName!!, sslIssuedToOName!!, sslIssuedToUName!!, sslIssuedByCName!!, sslIssuedByOName!!, sslIssuedByUName!!,
775 sslStartDateLong, sslEndDateLong)
777 // Update the pinned IP addresses if new ones are checked.
778 if (currentIpAddressesRadioButton.isChecked)
779 domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses!!)
782 private fun populateDomainsListView(highlightedDomainDatabaseId: Int, domainsListViewPosition: Int) {
783 // Get a cursor with the current contents of the domains database.
784 val domainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain
786 // Setup the domains cursor adapter.
787 val domainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, domainsCursor, false) {
788 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
789 // Inflate the individual item layout.
790 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
793 override fun bindView(view: View, context: Context, cursor: Cursor) {
794 // Get a handle for the domain name text view.
795 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
797 // Get the domain name string.
798 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME))
800 // Set the domain name.
801 domainNameTextView.text = domainNameString
805 // get a handle for the current domains listview.
806 domainsListView = findViewById(R.id.domains_listview)
808 // Update the list view.
809 domainsListView!!.adapter = domainsCursorAdapter
811 // Restore the scroll position.
812 domainsListView!!.setSelection(domainsListViewPosition)
814 // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain.
815 if (twoPanedMode && domainsCursor.count > 0) { // Two-paned mode is enabled and there is at least one domain.
816 // Initialize the highlighted domain position tracker.
817 var highlightedDomainPosition = 0
819 // Get the cursor position for the highlighted domain.
820 for (i in 0 until domainsCursor.count) {
821 // Move to position `i` in the cursor.
822 domainsCursor.moveToPosition(i)
824 // Get the database ID for this position.
825 val currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ID))
827 // Set the highlighted domain position if the database ID for this matches the highlighted domain database ID.
828 if (highlightedDomainDatabaseId == currentDatabaseId)
829 highlightedDomainPosition = i
832 // Select the highlighted domain.
833 domainsListView!!.setItemChecked(highlightedDomainPosition, true)
835 // Move to the highlighted domain.
836 domainsCursor.moveToPosition(highlightedDomainPosition)
838 // Get the database ID for the highlighted domain.
839 currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ID))
841 // Create an arguments bundle.
842 val argumentsBundle = Bundle()
844 // Store the domain settings in the arguments bundle.
845 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
846 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
848 // Instantiate a new domain settings fragment.
849 val domainSettingsFragment = DomainSettingsFragment()
851 // Add the arguments bundle to the domain settings fragment.
852 domainSettingsFragment.arguments = argumentsBundle
854 // Display the domain settings fragment.
855 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit()
857 // Enable the delete options menu items.
858 deleteMenuItem.isEnabled = true
859 } else if (twoPanedMode) { // Two-paned mode is enabled but there are no domains.
860 // Disable the delete menu item.
861 deleteMenuItem.isEnabled = false
865 override fun dismissSnackbar() {
866 // Dismiss the undo delete snackbar if it is shown.
867 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
868 // Dismiss the snackbar.
869 undoDeleteSnackbar!!.dismiss()
873 public override fun onDestroy() {
874 // Close the domains database helper.
875 domainsDatabaseHelper.close()
877 // Run the default commands.