2 * Copyright 2017-2024 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.view.Menu
27 import android.view.MenuItem
28 import android.view.View
29 import android.view.ViewGroup
30 import android.view.WindowManager
31 import android.widget.EditText
32 import android.widget.ListView
33 import android.widget.RadioButton
34 import android.widget.ScrollView
35 import android.widget.Spinner
36 import android.widget.TextView
38 import androidx.activity.OnBackPressedCallback
39 import androidx.appcompat.app.AppCompatActivity
40 import androidx.appcompat.widget.SwitchCompat
41 import androidx.appcompat.widget.Toolbar
42 import androidx.core.app.NavUtils
43 import androidx.cursoradapter.widget.CursorAdapter
44 import androidx.fragment.app.DialogFragment
45 import androidx.preference.PreferenceManager
47 import com.google.android.material.floatingactionbutton.FloatingActionButton
48 import com.google.android.material.snackbar.Snackbar
50 import com.stoutner.privacybrowser.R
51 import com.stoutner.privacybrowser.dialogs.AddDomainDialog
52 import com.stoutner.privacybrowser.dialogs.AddDomainDialog.AddDomainListener
53 import com.stoutner.privacybrowser.fragments.DomainSettingsFragment
54 import com.stoutner.privacybrowser.fragments.DomainsListFragment
55 import com.stoutner.privacybrowser.fragments.DomainsListFragment.DismissSnackbarInterface
56 import com.stoutner.privacybrowser.fragments.DomainsListFragment.SaveDomainSettingsInterface
57 import com.stoutner.privacybrowser.helpers.DOMAIN_NAME
58 import com.stoutner.privacybrowser.helpers.ID
59 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
61 // Define the public constants.
62 const val CLOSE_ON_BACK = "close_on_back"
63 const val CURRENT_IP_ADDRESSES = "current_ip_addresses"
64 const val LOAD_DOMAIN = "load_domain"
65 const val SSL_END_DATE = "ssl_end_date"
66 const val SSL_ISSUED_BY_CNAME = "ssl_issued_by_cname"
67 const val SSL_ISSUED_BY_ONAME = "ssl_issued_by_oname"
68 const val SSL_ISSUED_BY_UNAME = "ssl_issued_by_uname"
69 const val SSL_ISSUED_TO_CNAME = "ssl_issued_to_cname"
70 const val SSL_ISSUED_TO_ONAME = "ssl_issued_to_oname"
71 const val SSL_ISSUED_TO_UNAME = "ssl_issued_to_uname"
72 const val SSL_START_DATE = "ssl_start_date"
74 // Define the class constants.
75 private const val DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_id"
76 private const val DOMAIN_SETTINGS_DISPLAYED = "domain_settings_displayed"
77 private const val DOMAIN_SETTINGS_SCROLL_Y = "domain_settings_scroll_y"
78 private const val LISTVIEW_POSITION = "listview_position"
80 class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarInterface, SaveDomainSettingsInterface {
82 // Define the public variables.
83 var currentDomainDatabaseId = 0 // Used in `DomainsListFragment`.
84 var dismissingSnackbar = false // Used in `DomainsListFragment`.
85 var domainsListViewPosition = 0 // Used in `DomainsListFragment`.
86 var sslEndDateLong: Long = 0 // Used in `DomainsSettingsFragment`.
87 var sslStartDateLong: Long = 0 // Used in `DomainSettingsFragment`.
88 var twoPanedMode = false // Used in `DomainsListFragment`.
90 // Declare the public views. They are used in `DomainsListFragment`.
91 lateinit var deleteMenuItem: MenuItem
93 // Declare the SSL certificate and IP address strings.
94 var currentIpAddresses: String? = null // Used in `DomainSettingsFragment`.
95 var sslIssuedToCName: String? = null // Used in `DomainSettingsFragment`.
96 var sslIssuedToOName: String? = null // Used in `DomainSettingsFragment`.
97 var sslIssuedToUName: String? = null // Used in `DomainSettingsFragment`.
98 var sslIssuedByCName: String? = null // Used in `DomainSettingsFragment`.
99 var sslIssuedByOName: String? = null // Used in `DomainSettingsFragment`.
100 var sslIssuedByUName: String? = null // Used in `DomainSettingsFragment`.
103 // Declare the class views.
104 private lateinit var addDomainFAB: FloatingActionButton
105 private lateinit var coordinatorLayout: View
106 private var domainsListView: ListView? = null
107 private var undoDeleteSnackbar: Snackbar? = null
109 // Declare the class variables.
110 private lateinit var domainsDatabaseHelper: DomainsDatabaseHelper
112 // Define the class variables.
113 private var appRestarted = false
114 private var closeActivityAfterDismissingSnackbar = false
115 private var closeOnBack = false
116 private var deletedDomainPosition = 0
117 private var domainSettingsDatabaseIdBeforeRestart = 0
118 private var domainSettingsDisplayedBeforeRestart = false
119 private var domainSettingsScrollY = 0
120 private var goDirectlyToDatabaseId = -1
122 override fun onCreate(savedInstanceState: Bundle?) {
123 // Get a handle for the shared preferences.
124 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
126 // Get the preferences.
127 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
128 val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
130 // Disable screenshots if not allowed.
131 if (!allowScreenshots) {
132 window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
135 // Run the default commands.
136 super.onCreate(savedInstanceState)
138 // Process the saved instance state if it is not null.
139 if (savedInstanceState != null) {
140 // Extract the values from the saved instance state if it is not null.
141 domainsListViewPosition = savedInstanceState.getInt(LISTVIEW_POSITION)
142 domainSettingsDisplayedBeforeRestart = savedInstanceState.getBoolean(DOMAIN_SETTINGS_DISPLAYED)
143 domainSettingsDatabaseIdBeforeRestart = savedInstanceState.getInt(DOMAIN_SETTINGS_DATABASE_ID)
144 domainSettingsScrollY = savedInstanceState.getInt(DOMAIN_SETTINGS_SCROLL_Y)
146 // Set the app restarted flag.
150 // Get the launching intent
153 // Extract the domain to load if there is one. `-1` is the default value.
154 goDirectlyToDatabaseId = intent.getIntExtra(LOAD_DOMAIN, -1)
156 // Get the status of close-on-back, which is true when the domains activity is called from the options menu.
157 closeOnBack = intent.getBooleanExtra(CLOSE_ON_BACK, false)
159 // Store the current SSL certificate information in class variables.
160 sslIssuedToCName = intent.getStringExtra(SSL_ISSUED_TO_CNAME)
161 sslIssuedToOName = intent.getStringExtra(SSL_ISSUED_TO_ONAME)
162 sslIssuedToUName = intent.getStringExtra(SSL_ISSUED_TO_UNAME)
163 sslIssuedByCName = intent.getStringExtra(SSL_ISSUED_BY_CNAME)
164 sslIssuedByOName = intent.getStringExtra(SSL_ISSUED_BY_ONAME)
165 sslIssuedByUName = intent.getStringExtra(SSL_ISSUED_BY_UNAME)
166 sslStartDateLong = intent.getLongExtra(SSL_START_DATE, 0)
167 sslEndDateLong = intent.getLongExtra(SSL_END_DATE, 0)
168 currentIpAddresses = intent.getStringExtra(CURRENT_IP_ADDRESSES)
170 // Set the content view.
172 setContentView(R.layout.domains_bottom_appbar)
174 setContentView(R.layout.domains_top_appbar)
176 // Get handles for the views.
177 coordinatorLayout = findViewById(R.id.domains_coordinatorlayout)
178 val toolbar = findViewById<Toolbar>(R.id.domains_toolbar)
179 addDomainFAB = findViewById(R.id.add_domain_fab)
181 // Set the support action bar.
182 setSupportActionBar(toolbar)
184 // Get a handle for the action bar.
185 val actionBar = supportActionBar!!
187 // Set the back arrow on the action bar.
188 actionBar.setDisplayHomeAsUpEnabled(true)
190 // Initialize the database handler.
191 domainsDatabaseHelper = DomainsDatabaseHelper(this)
193 // Determine if the device is in two pane mode. `domain_settings_fragment_container` does not exist on devices with a width less than 900dp.
194 twoPanedMode = (findViewById<View?>(R.id.domain_settings_fragment_container) != null)
196 // Configure the add domain floating action button.
197 addDomainFAB.setOnClickListener {
198 // Create an add domain dialog.
199 val addDomainDialog: DialogFragment = AddDomainDialog()
201 // Show the add domain dialog.
202 addDomainDialog.show(supportFragmentManager, resources.getString(R.string.add_domain))
205 // Control what the system back command does.
206 val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
207 override fun handleOnBackPressed() {
208 if (twoPanedMode) { // The device is in two-paned mode.
209 // Save the current domain settings if the domain settings fragment is displayed.
210 if (findViewById<View?>(R.id.domain_settings_scrollview) != null)
211 saveDomainSettings(coordinatorLayout)
213 // Dismiss the undo delete snackbar if it is shown.
214 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
215 // Set the close flag.
216 closeActivityAfterDismissingSnackbar = true
218 // Dismiss the snackbar.
219 undoDeleteSnackbar!!.dismiss()
224 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
225 // Save the current domain settings.
226 saveDomainSettings(coordinatorLayout)
230 } else if (findViewById<View?>(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and domain settings fragment is displayed.
231 // Save the current domain settings.
232 saveDomainSettings(coordinatorLayout)
234 // Instantiate a new domains list fragment.
235 val domainsListFragment = DomainsListFragment()
237 // Display the domains list fragment.
238 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
240 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
241 populateDomainsListView(-1, domainsListViewPosition)
243 // Show the add domain floating action button.
246 // Hide the delete menu item.
247 deleteMenuItem.isVisible = false
248 } else { // The device is in single-paned mode and the domain list fragment is displayed.
249 // Dismiss the undo delete SnackBar if it is shown.
250 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
251 // Set the close flag.
252 closeActivityAfterDismissingSnackbar = true
254 // Dismiss the snackbar.
255 undoDeleteSnackbar!!.dismiss()
264 // Register the on back pressed callback.
265 onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
268 override fun onCreateOptionsMenu(menu: Menu): Boolean {
270 menuInflater.inflate(R.menu.domains_options_menu, menu)
272 // Get a handle for the delete menu item.
273 deleteMenuItem = menu.findItem(R.id.delete_domain)
275 // Only display the delete menu item (initially) in two-paned mode.
276 deleteMenuItem.isVisible = twoPanedMode
278 // Display the fragments. This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated.
279 if (appRestarted && domainSettingsDisplayedBeforeRestart) { // The app was restarted, possibly because the device was rotated, and domain settings were displayed previously.
280 // Reset the app restarted flag.
283 if (twoPanedMode) { // The device is in two-paned mode.
284 // Initialize the domains list fragment.
285 val domainsListFragment = DomainsListFragment()
287 // Display the domains list fragment.
288 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
290 // Populate the list of domains and highlight the domain that was highlighted before the restart.
291 populateDomainsListView(domainSettingsDatabaseIdBeforeRestart, domainsListViewPosition)
292 } else { // The device is in single-paned mode.
293 // Store the current domain database ID.
294 currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRestart
296 // Create an arguments bundle.
297 val argumentsBundle = Bundle()
299 // Add the domain settings arguments.
300 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
301 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
303 // Instantiate a new domain settings fragment.
304 val domainSettingsFragment = DomainSettingsFragment()
306 // Add the arguments bundle to the domain settings fragment.
307 domainSettingsFragment.arguments = argumentsBundle
309 // Show the delete menu item.
310 deleteMenuItem.isVisible = true
312 // Hide the add domain floating action button.
315 // Display the domain settings fragment.
316 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
318 } else { // The device was not restarted or, if it was, domain settings were not displayed previously.
319 if (goDirectlyToDatabaseId >= 0) { // Load the indicated domain settings.
320 // Store the current domain database ID.
321 currentDomainDatabaseId = goDirectlyToDatabaseId
323 // Check if the device is in two-paned mode.
324 if (twoPanedMode) { // The device is in two-paned mode.
325 // Instantiate a new domains list fragment.
326 val domainsListFragment = DomainsListFragment()
328 // Display the domains list fragment.
329 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
331 // Populate the list of domains.
332 populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition)
333 } else { // The device is in single-paned mode.
334 // Create an arguments bundle.
335 val argumentsBundle = Bundle()
337 // Add the domain settings to arguments bundle.
338 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId)
339 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
341 // Instantiate a new domain settings fragment.
342 val domainSettingsFragment = DomainSettingsFragment()
344 // Add the arguments bundle to the domain settings fragment.
345 domainSettingsFragment.arguments = argumentsBundle
347 // Show the delete menu item.
348 deleteMenuItem.isVisible = true
350 // Hide the add domain floating action button.
353 // Display the domain settings fragment.
354 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
356 } else { // Highlight the first domain.
357 // Instantiate a new domains list fragment.
358 val domainsListFragment = DomainsListFragment()
360 // Display the domain list fragment.
361 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
363 // Populate the list of domains. `-1` highlights the first domain.
364 populateDomainsListView(-1, domainsListViewPosition)
372 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
373 // Run the command according to the selected menu item.
374 when (menuItem.itemId) {
375 android.R.id.home -> { // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
376 // Check if the device is in two-paned mode.
377 if (twoPanedMode) { // The device is in two-paned mode.
378 // Save the current domain settings if the domain settings fragment is displayed.
379 if (findViewById<View?>(R.id.domain_settings_scrollview) != null)
380 saveDomainSettings(coordinatorLayout)
382 // Dismiss the undo delete snackbar if it is shown.
383 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
384 // Set the close flag.
385 closeActivityAfterDismissingSnackbar = true
387 // Dismiss the snackbar.
388 undoDeleteSnackbar!!.dismiss()
393 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
394 // Save the current domain settings.
395 saveDomainSettings(coordinatorLayout)
399 } else if (findViewById<View?>(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed.
400 // Save the current domain settings.
401 saveDomainSettings(coordinatorLayout)
403 // Instantiate a new domains list fragment.
404 val domainsListFragment = DomainsListFragment()
406 // Display the domains list fragment.
407 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
409 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
410 populateDomainsListView(-1, domainsListViewPosition)
412 // Show the add domain floating action button.
415 // Hide the delete menu item.
416 deleteMenuItem.isVisible = false
417 } else { // The device is in single-paned mode and domains list fragment is displayed.
418 // Dismiss the undo delete snackbar if it is shown.
419 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
420 // Set the close flag.
421 closeActivityAfterDismissingSnackbar = true
423 // Dismiss the snackbar.
424 undoDeleteSnackbar!!.dismiss()
432 R.id.delete_domain -> { // Delete.
433 // Get a handle for the activity (used in an inner class below).
434 val activity: Activity = this
436 // Check to see if the domain settings were loaded directly for editing of this app in single-paned mode.
437 if (closeOnBack && !twoPanedMode) { // The activity should delete the domain settings and exit straight to the the main WebView activity.
438 // Delete the selected domain.
439 domainsDatabaseHelper.deleteDomain(currentDomainDatabaseId)
442 NavUtils.navigateUpFromSameTask(activity)
443 } else { // A snackbar should be shown before deleting the domain settings.
444 // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists.
447 // Store a copy of the current domain database ID because it could change while the snackbar is displayed.
448 val databaseIdToDelete = currentDomainDatabaseId
450 // Update the fragments and menu items.
451 if (twoPanedMode) { // Two-paned mode.
452 // Store the deleted domain position, which is needed if undo is selected in the snackbar.
453 deletedDomainPosition = domainsListView!!.checkedItemPosition
455 // Disable the delete menu item.
456 deleteMenuItem.isEnabled = false
458 // Remove the domain settings fragment.
459 supportFragmentManager.beginTransaction().remove(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!!).commitNow()
460 } else { // Single-paned mode.
461 // Instantiate a new domains list fragment.
462 val domainsListFragment = DomainsListFragment()
464 // Display the domains list fragment.
465 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
467 // Show the add domain floating action button.
470 // Hide the delete menu item.
471 deleteMenuItem.isVisible = false
474 // Get a cursor that does not show the domain to be deleted.
475 val domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete)
477 // Setup the domains pending delete cursor adapter.
478 val domainsPendingDeleteCursorAdapter: CursorAdapter = object : CursorAdapter(this, domainsPendingDeleteCursor, false) {
479 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
480 // Inflate the individual item layout.
481 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
484 override fun bindView(view: View, context: Context, cursor: Cursor) {
485 // Get the domain name string.
486 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME))
488 // Get a handle for the domain name text view.
489 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
491 // Display the domain name.
492 domainNameTextView.text = domainNameString
496 // Update the handle for the current domains list view.
497 domainsListView = findViewById(R.id.domains_listview)
499 // Update the list view.
500 domainsListView!!.adapter = domainsPendingDeleteCursorAdapter
502 // Display a snackbar.
503 undoDeleteSnackbar = Snackbar.make(domainsListView!!, R.string.domain_deleted, Snackbar.LENGTH_LONG)
504 .setAction(R.string.undo) {} // Do nothing because everything will be handled by `onDismissed()` below.
505 .addCallback(object : Snackbar.Callback() {
506 override fun onDismissed(snackbar: Snackbar, event: Int) {
507 // Run commands based on the event.
508 if (event == DISMISS_EVENT_ACTION) { // The user pushed the `Undo` button.
509 // Create an arguments bundle.
510 val argumentsBundle = Bundle()
512 // Store the domains settings in the arguments bundle.
513 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete)
514 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
516 // Instantiate a new domain settings fragment.
517 val domainSettingsFragment = DomainSettingsFragment()
519 // Add the arguments bundle to the domain settings fragment.
520 domainSettingsFragment.arguments = argumentsBundle
522 // Display the correct fragments.
523 if (twoPanedMode) { // The device in in two-paned mode.
524 // Get a cursor with the current contents of the domains database.
525 val undoDeleteDomainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain
527 // Setup the domains cursor adapter.
528 val undoDeleteDomainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, undoDeleteDomainsCursor, false) {
529 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
530 // Inflate the individual item layout.
531 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
534 override fun bindView(view: View, context: Context, cursor: Cursor) {
535 /// Get the domain name string.
536 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME))
538 // Get a handle for the domain name text view.
539 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
541 // Display the domain name.
542 domainNameTextView.text = domainNameString
546 // Update the domains list view.
547 domainsListView!!.adapter = undoDeleteDomainsCursorAdapter
549 // Select the previously deleted domain in the list view.
550 domainsListView!!.setItemChecked(deletedDomainPosition, true)
552 // Display the domain settings fragment.
553 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commitNow()
555 // Enable the delete menu item.
556 deleteMenuItem.isEnabled = true
557 } else { // The device in in one-paned mode.
558 // Display the domain settings fragment.
559 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
561 // Hide the add domain floating action button.
564 // Show and enable the delete menu item.
565 deleteMenuItem.isVisible = true
567 // Display the domain settings fragment.
568 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
570 } else { // The snackbar was dismissed without the undo button being pushed.
571 // Delete the selected domain.
572 val rowsDeleted = domainsDatabaseHelper.deleteDomain(databaseIdToDelete)
574 // Enable the delete menu item.
575 // The rows deleted should always be greater than 0, but in all cases they should be greater than -1.
576 // This has the effect of tricking the compiler into waiting until after the delete finishes to reenable the delete menu item,
577 // because the compiler (probably) can't tell that the response will never be less than -1, so it doesn't compile out the delay.
578 if (rowsDeleted > -1) {
579 // Enable or show the delete menu item according to the display mode.
581 deleteMenuItem.isEnabled = true
583 deleteMenuItem.isVisible = true
585 // Reset the dismissing snackbar tracker.
586 dismissingSnackbar = false
589 // Close the activity if back was pressed.
590 if (closeActivityAfterDismissingSnackbar)
591 NavUtils.navigateUpFromSameTask(activity)
596 // Show the Snackbar.
597 undoDeleteSnackbar!!.show()
602 // Consume the event.
606 override fun onSaveInstanceState(outState: Bundle) {
607 // Run the default commands.
608 super.onSaveInstanceState(outState)
610 // Get a handle for the domain settings scrollview.
611 val domainSettingsScrollView = findViewById<ScrollView>(R.id.domain_settings_scrollview)
613 // Check to see if the domain settings scrollview exists.
614 if (domainSettingsScrollView == null) { // The domain settings are not displayed.
615 // Store the domain settings status in the bundle.
616 outState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false)
617 outState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1)
618 outState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0)
619 } else { // The domain settings are displayed.
620 // Save any changes that have been made to the domain settings.
621 saveDomainSettings(coordinatorLayout)
623 // Get the domain settings scroll Y.
624 val domainSettingsScrollY = domainSettingsScrollView.scrollY
626 // Store the domain settings status in the bundle.
627 outState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true)
628 outState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId)
629 outState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY)
632 // Check to see if the domains listview exists.
633 if (domainsListView != null) {
634 // Get the domains listview position.
635 val domainsListViewPosition = domainsListView!!.firstVisiblePosition
637 // Store the listview position in the bundle.
638 outState.putInt(LISTVIEW_POSITION, domainsListViewPosition)
642 override fun addDomain(dialogFragment: DialogFragment) {
643 // Dismiss the undo delete snackbar if it is currently displayed.
644 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown)
645 undoDeleteSnackbar!!.dismiss()
647 // Get a handle for the domain name edit text.
648 val domainNameEditText = dialogFragment.dialog!!.findViewById<EditText>(R.id.domain_name_edittext)
650 // Get the domain name string.
651 val domainNameString = domainNameEditText.text.toString()
653 // Create the domain and store the database ID in the current domain database ID.
654 currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString)
656 // Display the newly created domain.
657 if (twoPanedMode) { // The device in in two-paned mode.
658 populateDomainsListView(currentDomainDatabaseId, 0)
659 } else { // The device is in single-paned mode.
660 // Hide the add domain floating action button.
663 // Show and enable the delete menu item.
664 deleteMenuItem.isVisible = true
666 // Create an arguments bundle.
667 val argumentsBundle = Bundle()
669 // Add the domain settings to the arguments bundle. The scroll Y should always be `0` on a new domain.
670 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
671 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, 0)
673 // Instantiate a new domain settings fragment.
674 val domainSettingsFragment = DomainSettingsFragment()
676 // Add the arguments bundle to the domain setting fragment.
677 domainSettingsFragment.arguments = argumentsBundle
679 // Display the domain settings fragment.
680 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
684 override fun saveDomainSettings(view: View) {
685 // Get handles for the domain settings.
686 val domainNameEditText = view.findViewById<EditText>(R.id.domain_settings_name_edittext)
687 val javaScriptSpinner = view.findViewById<Spinner>(R.id.javascript_spinner)
688 val cookiesSpinner = view.findViewById<Spinner>(R.id.cookies_spinner)
689 val domStorageSpinner = view.findViewById<Spinner>(R.id.dom_storage_spinner)
690 val userAgentSpinner = view.findViewById<Spinner>(R.id.user_agent_spinner)
691 val customUserAgentEditText = view.findViewById<EditText>(R.id.custom_user_agent_edittext)
692 val easyListSpinner = view.findViewById<Spinner>(R.id.easylist_spinner)
693 val easyPrivacySpinner = view.findViewById<Spinner>(R.id.easyprivacy_spinner)
694 val fanboysAnnoyanceSpinner = view.findViewById<Spinner>(R.id.fanboys_annoyance_list_spinner)
695 val fanboysSocialBlockingSpinner = view.findViewById<Spinner>(R.id.fanboys_social_blocking_list_spinner)
696 val ultraListSpinner = view.findViewById<Spinner>(R.id.ultralist_spinner)
697 val ultraPrivacySpinner = view.findViewById<Spinner>(R.id.ultraprivacy_spinner)
698 val blockAllThirdPartyRequestsSpinner = view.findViewById<Spinner>(R.id.block_all_third_party_requests_spinner)
699 val fontSizeSpinner = view.findViewById<Spinner>(R.id.font_size_spinner)
700 val customFontSizeEditText = view.findViewById<EditText>(R.id.custom_font_size_edittext)
701 val swipeToRefreshSpinner = view.findViewById<Spinner>(R.id.swipe_to_refresh_spinner)
702 val webViewThemeSpinner = view.findViewById<Spinner>(R.id.webview_theme_spinner)
703 val wideViewportSpinner = view.findViewById<Spinner>(R.id.wide_viewport_spinner)
704 val displayWebpageImagesSpinner = view.findViewById<Spinner>(R.id.display_images_spinner)
705 val pinnedSslCertificateSwitch = view.findViewById<SwitchCompat>(R.id.pinned_ssl_certificate_switch)
706 val currentWebsiteCertificateRadioButton = view.findViewById<RadioButton>(R.id.current_website_certificate_radiobutton)
707 val pinnedIpAddressesSwitch = view.findViewById<SwitchCompat>(R.id.pinned_ip_addresses_switch)
708 val currentIpAddressesRadioButton = view.findViewById<RadioButton>(R.id.current_ip_addresses_radiobutton)
710 // Extract the data for the domain settings.
711 val domainNameString = domainNameEditText.text.toString()
712 val javaScriptInt = javaScriptSpinner.selectedItemPosition
713 val cookiesInt = cookiesSpinner.selectedItemPosition
714 val domStorageInt = domStorageSpinner.selectedItemPosition
715 val userAgentSwitchPosition = userAgentSpinner.selectedItemPosition
716 val easyListInt = easyListSpinner.selectedItemPosition
717 val easyPrivacyInt = easyPrivacySpinner.selectedItemPosition
718 val fanboysAnnoyanceInt = fanboysAnnoyanceSpinner.selectedItemPosition
719 val fanboysSocialBlockingInt = fanboysSocialBlockingSpinner.selectedItemPosition
720 val ultraListInt = ultraListSpinner.selectedItemPosition
721 val ultraPrivacyInt = ultraPrivacySpinner.selectedItemPosition
722 val blockAllThirdPartyRequestsInt = blockAllThirdPartyRequestsSpinner.selectedItemPosition
723 val fontSizeSwitchPosition = fontSizeSpinner.selectedItemPosition
724 val swipeToRefreshInt = swipeToRefreshSpinner.selectedItemPosition
725 val webViewThemeInt = webViewThemeSpinner.selectedItemPosition
726 val wideViewportInt = wideViewportSpinner.selectedItemPosition
727 val displayWebpageImagesInt = displayWebpageImagesSpinner.selectedItemPosition
728 val pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked
729 val pinnedIpAddress = pinnedIpAddressesSwitch.isChecked
731 // Get the user agent name.
732 val userAgentName: String = when (userAgentSwitchPosition) {
733 // Set the user agent name to be `System default user agent`.
734 DOMAINS_SYSTEM_DEFAULT_USER_AGENT -> resources.getString(R.string.system_default_user_agent)
736 // Set the user agent name to be the custom user agent.
737 DOMAINS_CUSTOM_USER_AGENT -> customUserAgentEditText.text.toString()
740 // Get the array of user agent names.
741 val userAgentNameArray = resources.getStringArray(R.array.user_agent_names)
743 // 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.
744 userAgentNameArray[userAgentSwitchPosition - 1]
748 // Initialize the font size integer. `0` indicates the system default font size.
751 // Use a custom font size if it is selected.
752 if (fontSizeSwitchPosition == 1)
753 fontSizeInt = customFontSizeEditText.text.toString().toInt()
755 // Save the domain settings.
756 domainsDatabaseHelper.updateDomain(currentDomainDatabaseId, domainNameString, javaScriptInt, cookiesInt, domStorageInt, userAgentName, easyListInt, easyPrivacyInt, fanboysAnnoyanceInt,
757 fanboysSocialBlockingInt, ultraListInt, ultraPrivacyInt, blockAllThirdPartyRequestsInt, fontSizeInt, swipeToRefreshInt, webViewThemeInt, wideViewportInt, displayWebpageImagesInt,
758 pinnedSslCertificate, pinnedIpAddress)
760 // Update the pinned SSL certificate if a new one is checked.
761 if (currentWebsiteCertificateRadioButton.isChecked)
762 domainsDatabaseHelper.updatePinnedSslCertificate(currentDomainDatabaseId, sslIssuedToCName!!, sslIssuedToOName!!, sslIssuedToUName!!, sslIssuedByCName!!, sslIssuedByOName!!, sslIssuedByUName!!,
763 sslStartDateLong, sslEndDateLong)
765 // Update the pinned IP addresses if new ones are checked.
766 if (currentIpAddressesRadioButton.isChecked)
767 domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses!!)
770 private fun populateDomainsListView(highlightedDomainDatabaseId: Int, domainsListViewPosition: Int) {
771 // Get a cursor with the current contents of the domains database.
772 val domainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain
774 // Setup the domains cursor adapter.
775 val domainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, domainsCursor, false) {
776 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
777 // Inflate the individual item layout.
778 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
781 override fun bindView(view: View, context: Context, cursor: Cursor) {
782 // Get a handle for the domain name text view.
783 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
785 // Get the domain name string.
786 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME))
788 // Set the domain name.
789 domainNameTextView.text = domainNameString
793 // get a handle for the current domains listview.
794 domainsListView = findViewById(R.id.domains_listview)
796 // Update the list view.
797 domainsListView!!.adapter = domainsCursorAdapter
799 // Restore the scroll position.
800 domainsListView!!.setSelection(domainsListViewPosition)
802 // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain.
803 if (twoPanedMode && domainsCursor.count > 0) { // Two-paned mode is enabled and there is at least one domain.
804 // Initialize the highlighted domain position tracker.
805 var highlightedDomainPosition = 0
807 // Get the cursor position for the highlighted domain.
808 for (i in 0 until domainsCursor.count) {
809 // Move to position `i` in the cursor.
810 domainsCursor.moveToPosition(i)
812 // Get the database ID for this position.
813 val currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ID))
815 // Set the highlighted domain position if the database ID for this matches the highlighted domain database ID.
816 if (highlightedDomainDatabaseId == currentDatabaseId)
817 highlightedDomainPosition = i
820 // Select the highlighted domain.
821 domainsListView!!.setItemChecked(highlightedDomainPosition, true)
823 // Move to the highlighted domain.
824 domainsCursor.moveToPosition(highlightedDomainPosition)
826 // Get the database ID for the highlighted domain.
827 currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ID))
829 // Create an arguments bundle.
830 val argumentsBundle = Bundle()
832 // Store the domain settings in the arguments bundle.
833 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
834 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
836 // Instantiate a new domain settings fragment.
837 val domainSettingsFragment = DomainSettingsFragment()
839 // Add the arguments bundle to the domain settings fragment.
840 domainSettingsFragment.arguments = argumentsBundle
842 // Display the domain settings fragment.
843 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit()
845 // Enable the delete options menu items.
846 deleteMenuItem.isEnabled = true
847 } else if (twoPanedMode) { // Two-paned mode is enabled but there are no domains.
848 // Disable the delete menu item.
849 deleteMenuItem.isEnabled = false
853 override fun dismissSnackbar() {
854 // Dismiss the undo delete snackbar if it is shown.
855 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
856 // Dismiss the snackbar.
857 undoDeleteSnackbar!!.dismiss()
861 public override fun onDestroy() {
862 // Close the domains database helper.
863 domainsDatabaseHelper.close()
865 // Run the default commands.