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
38 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.DomainsDatabaseHelper
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"
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"
79 class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarInterface, SaveDomainSettingsInterface {
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`.
89 // Declare the public views. They are used in `DomainsListFragment`.
90 lateinit var deleteMenuItem: MenuItem
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`.
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
108 // Declare the class variables.
109 private lateinit var domainsDatabaseHelper: DomainsDatabaseHelper
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
121 override fun onCreate(savedInstanceState: Bundle?) {
122 // Get a handle for the shared preferences.
123 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
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)
129 // Disable screenshots if not allowed.
130 if (!allowScreenshots) {
131 window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
134 // Run the default commands.
135 super.onCreate(savedInstanceState)
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)
145 // Set the app restarted flag.
149 // Get the launching intent
152 // Extract the domain to load if there is one. `-1` is the default value.
153 goDirectlyToDatabaseId = intent.getIntExtra(LOAD_DOMAIN, -1)
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)
158 // Get the current URL.
159 val currentUrl = intent.getStringExtra(CURRENT_URL)
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)
172 // Set the content view.
174 setContentView(R.layout.domains_bottom_appbar)
176 setContentView(R.layout.domains_top_appbar)
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)
183 // Set the support action bar.
184 setSupportActionBar(toolbar)
186 // Get a handle for the action bar.
187 val actionBar = supportActionBar!!
189 // Set the back arrow on the action bar.
190 actionBar.setDisplayHomeAsUpEnabled(true)
192 // Initialize the database handler.
193 domainsDatabaseHelper = DomainsDatabaseHelper(this)
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)
198 // Configure the add domain floating action button.
199 addDomainFAB.setOnClickListener {
200 // Create an add domain dialog.
201 val addDomainDialog: DialogFragment = addDomain(currentUrl)
203 // Show the add domain dialog.
204 addDomainDialog.show(supportFragmentManager, resources.getString(R.string.add_domain))
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)
215 // Dismiss the undo delete snackbar if it is shown.
216 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
217 // Set the close flag.
218 closeActivityAfterDismissingSnackbar = true
220 // Dismiss the snackbar.
221 undoDeleteSnackbar!!.dismiss()
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)
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)
236 // Instantiate a new domains list fragment.
237 val domainsListFragment = DomainsListFragment()
239 // Display the domains list fragment.
240 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
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)
245 // Show the add domain floating action button.
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
256 // Dismiss the snackbar.
257 undoDeleteSnackbar!!.dismiss()
266 // Register the on back pressed callback.
267 onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
270 override fun onCreateOptionsMenu(menu: Menu): Boolean {
272 menuInflater.inflate(R.menu.domains_options_menu, menu)
274 // Get a handle for the delete menu item.
275 deleteMenuItem = menu.findItem(R.id.delete_domain)
277 // Only display the delete menu item (initially) in two-paned mode.
278 deleteMenuItem.isVisible = twoPanedMode
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.
285 if (twoPanedMode) { // The device is in two-paned mode.
286 // Initialize the domains list fragment.
287 val domainsListFragment = DomainsListFragment()
289 // Display the domains list fragment.
290 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
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
298 // Create an arguments bundle.
299 val argumentsBundle = Bundle()
301 // Add the domain settings arguments.
302 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
303 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
305 // Instantiate a new domain settings fragment.
306 val domainSettingsFragment = DomainSettingsFragment()
308 // Add the arguments bundle to the domain settings fragment.
309 domainSettingsFragment.arguments = argumentsBundle
311 // Show the delete menu item.
312 deleteMenuItem.isVisible = true
314 // Hide the add domain floating action button.
317 // Display the domain settings fragment.
318 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
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
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()
330 // Display the domains list fragment.
331 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
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()
339 // Add the domain settings to arguments bundle.
340 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId)
341 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
343 // Instantiate a new domain settings fragment.
344 val domainSettingsFragment = DomainSettingsFragment()
346 // Add the arguments bundle to the domain settings fragment.
347 domainSettingsFragment.arguments = argumentsBundle
349 // Show the delete menu item.
350 deleteMenuItem.isVisible = true
352 // Hide the add domain floating action button.
355 // Display the domain settings fragment.
356 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
358 } else { // Highlight the first domain.
359 // Instantiate a new domains list fragment.
360 val domainsListFragment = DomainsListFragment()
362 // Display the domain list fragment.
363 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
365 // Populate the list of domains. `-1` highlights the first domain.
366 populateDomainsListView(-1, domainsListViewPosition)
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)
384 // Dismiss the undo delete snackbar if it is shown.
385 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
386 // Set the close flag.
387 closeActivityAfterDismissingSnackbar = true
389 // Dismiss the snackbar.
390 undoDeleteSnackbar!!.dismiss()
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)
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)
405 // Instantiate a new domains list fragment.
406 val domainsListFragment = DomainsListFragment()
408 // Display the domains list fragment.
409 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
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)
414 // Show the add domain floating action button.
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
425 // Dismiss the snackbar.
426 undoDeleteSnackbar!!.dismiss()
434 R.id.delete_domain -> { // Delete.
435 // Get a handle for the activity (used in an inner class below).
436 val activity: Activity = this
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)
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.
449 // Store a copy of the current domain database ID because it could change while the snackbar is displayed.
450 val databaseIdToDelete = currentDomainDatabaseId
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
457 // Disable the delete menu item.
458 deleteMenuItem.isEnabled = false
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()
466 // Display the domains list fragment.
467 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
469 // Show the add domain floating action button.
472 // Hide the delete menu item.
473 deleteMenuItem.isVisible = false
476 // Get a cursor that does not show the domain to be deleted.
477 val domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete)
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)
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))
490 // Get a handle for the domain name text view.
491 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
493 // Display the domain name.
494 domainNameTextView.text = domainNameString
498 // Update the handle for the current domains list view.
499 domainsListView = findViewById(R.id.domains_listview)
501 // Update the list view.
502 domainsListView!!.adapter = domainsPendingDeleteCursorAdapter
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()
514 // Store the domains settings in the arguments bundle.
515 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete)
516 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
518 // Instantiate a new domain settings fragment.
519 val domainSettingsFragment = DomainSettingsFragment()
521 // Add the arguments bundle to the domain settings fragment.
522 domainSettingsFragment.arguments = argumentsBundle
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
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)
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))
540 // Get a handle for the domain name text view.
541 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
543 // Display the domain name.
544 domainNameTextView.text = domainNameString
548 // Update the domains list view.
549 domainsListView!!.adapter = undoDeleteDomainsCursorAdapter
551 // Select the previously deleted domain in the list view.
552 domainsListView!!.setItemChecked(deletedDomainPosition, true)
554 // Display the domain settings fragment.
555 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commitNow()
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()
563 // Hide the add domain floating action button.
566 // Show and enable the delete menu item.
567 deleteMenuItem.isVisible = true
569 // Display the domain settings fragment.
570 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
572 } else { // The snackbar was dismissed without the undo button being pushed.
573 // Delete the selected domain.
574 domainsDatabaseHelper.deleteDomain(databaseIdToDelete)
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.
582 deleteMenuItem.isEnabled = true
584 deleteMenuItem.isVisible = true
586 // Reset the dismissing snackbar tracker.
587 dismissingSnackbar = false
590 // Instantiate a handler running the main looper.
591 val handler = Handler(mainLooper)
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)
597 // Close the activity if back was pressed.
598 if (closeActivityAfterDismissingSnackbar)
599 NavUtils.navigateUpFromSameTask(activity)
604 // Show the Snackbar.
605 undoDeleteSnackbar!!.show()
610 // Consume the event.
614 override fun onSaveInstanceState(savedInstanceState: Bundle) {
615 // Run the default commands.
616 super.onSaveInstanceState(savedInstanceState)
618 // Get a handle for the domain settings scrollview.
619 val domainSettingsScrollView = findViewById<ScrollView>(R.id.domain_settings_scrollview)
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)
631 // Get the domain settings scroll Y.
632 val domainSettingsScrollY = domainSettingsScrollView.scrollY
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)
640 // Check to see if the domains listview exists.
641 if (domainsListView != null) {
642 // Get the domains listview position.
643 val domainsListViewPosition = domainsListView!!.firstVisiblePosition
645 // Store the listview position in the bundle.
646 savedInstanceState.putInt(LISTVIEW_POSITION, domainsListViewPosition)
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()
655 // Get a handle for the domain name edit text.
656 val domainNameEditText = dialogFragment.dialog!!.findViewById<EditText>(R.id.domain_name_edittext)
658 // Get the domain name string.
659 val domainNameString = domainNameEditText.text.toString()
661 // Create the domain and store the database ID in the current domain database ID.
662 currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString)
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.
671 // Show and enable the delete menu item.
672 deleteMenuItem.isVisible = true
674 // Create an arguments bundle.
675 val argumentsBundle = Bundle()
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)
681 // Instantiate a new domain settings fragment.
682 val domainSettingsFragment = DomainSettingsFragment()
684 // Add the arguments bundle to the domain setting fragment.
685 domainSettingsFragment.arguments = argumentsBundle
687 // Display the domain settings fragment.
688 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
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)
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
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)
746 // Set the user agent name to be the custom user agent.
747 DOMAINS_CUSTOM_USER_AGENT -> customUserAgentEditText.text.toString()
750 // Get the array of user agent names.
751 val userAgentNameArray = resources.getStringArray(R.array.user_agent_names)
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]
758 // Initialize the font size integer. `0` indicates the system default font size.
761 // Use a custom font size if it is selected.
762 if (fontSizeSwitchPosition == 1)
763 fontSizeInt = customFontSizeEditText.text.toString().toInt()
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)
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)
775 // Update the pinned IP addresses if new ones are checked.
776 if (currentIpAddressesRadioButton.isChecked)
777 domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses!!)
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
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)
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)
795 // Get the domain name string.
796 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME))
798 // Set the domain name.
799 domainNameTextView.text = domainNameString
803 // get a handle for the current domains listview.
804 domainsListView = findViewById(R.id.domains_listview)
806 // Update the list view.
807 domainsListView!!.adapter = domainsCursorAdapter
809 // Restore the scroll position.
810 domainsListView!!.setSelection(domainsListViewPosition)
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
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)
822 // Get the database ID for this position.
823 val currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID))
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
830 // Select the highlighted domain.
831 domainsListView!!.setItemChecked(highlightedDomainPosition, true)
833 // Move to the highlighted domain.
834 domainsCursor.moveToPosition(highlightedDomainPosition)
836 // Get the database ID for the highlighted domain.
837 currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID))
839 // Create an arguments bundle.
840 val argumentsBundle = Bundle()
842 // Store the domain settings in the arguments bundle.
843 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
844 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
846 // Instantiate a new domain settings fragment.
847 val domainSettingsFragment = DomainSettingsFragment()
849 // Add the arguments bundle to the domain settings fragment.
850 domainSettingsFragment.arguments = argumentsBundle
852 // Display the domain settings fragment.
853 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit()
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
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()
871 public override fun onDestroy() {
872 // Close the domains database helper.
873 domainsDatabaseHelper.close()
875 // Run the default commands.