2 * Copyright 2017-2022 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 class constants.
61 private const val DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_id"
62 private const val DOMAIN_SETTINGS_DISPLAYED = "domain_settings_displayed"
63 private const val DOMAIN_SETTINGS_SCROLL_Y = "domain_settings_scroll_y"
64 private const val LISTVIEW_POSITION = "listview_position"
66 class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarInterface, SaveDomainSettingsInterface {
68 // Define the public constants.
69 const val CLOSE_ON_BACK = "close_on_back"
70 const val CURRENT_IP_ADDRESSES = "current_ip_addresses"
71 const val CURRENT_URL = "current_url"
72 const val LOAD_DOMAIN = "load_domain"
73 const val SSL_END_DATE = "ssl_end_date"
74 const val SSL_ISSUED_BY_CNAME = "ssl_issued_by_cname"
75 const val SSL_ISSUED_BY_ONAME = "ssl_issued_by_oname"
76 const val SSL_ISSUED_BY_UNAME = "ssl_issued_by_uname"
77 const val SSL_ISSUED_TO_CNAME = "ssl_issued_to_cname"
78 const val SSL_ISSUED_TO_ONAME = "ssl_issued_to_oname"
79 const val SSL_ISSUED_TO_UNAME = "ssl_issued_to_uname"
80 const val SSL_START_DATE = "ssl_start_date"
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 // Get the current URL.
160 val currentUrl = intent.getStringExtra(CURRENT_URL)
162 // Store the current SSL certificate information in class variables.
163 sslIssuedToCName = intent.getStringExtra(SSL_ISSUED_TO_CNAME)
164 sslIssuedToOName = intent.getStringExtra(SSL_ISSUED_TO_ONAME)
165 sslIssuedToUName = intent.getStringExtra(SSL_ISSUED_TO_UNAME)
166 sslIssuedByCName = intent.getStringExtra(SSL_ISSUED_BY_CNAME)
167 sslIssuedByOName = intent.getStringExtra(SSL_ISSUED_BY_ONAME)
168 sslIssuedByUName = intent.getStringExtra(SSL_ISSUED_BY_UNAME)
169 sslStartDateLong = intent.getLongExtra(SSL_START_DATE, 0)
170 sslEndDateLong = intent.getLongExtra(SSL_END_DATE, 0)
171 currentIpAddresses = intent.getStringExtra(CURRENT_IP_ADDRESSES)
173 // Set the content view.
175 setContentView(R.layout.domains_bottom_appbar)
177 setContentView(R.layout.domains_top_appbar)
179 // Get handles for the views.
180 coordinatorLayout = findViewById(R.id.domains_coordinatorlayout)
181 val toolbar = findViewById<Toolbar>(R.id.domains_toolbar)
182 addDomainFAB = findViewById(R.id.add_domain_fab)
184 // Set the support action bar.
185 setSupportActionBar(toolbar)
187 // Get a handle for the action bar.
188 val actionBar = supportActionBar!!
190 // Set the back arrow on the action bar.
191 actionBar.setDisplayHomeAsUpEnabled(true)
193 // Initialize the database handler.
194 domainsDatabaseHelper = DomainsDatabaseHelper(this)
196 // Determine if the device is in two pane mode. `domain_settings_fragment_container` does not exist on devices with a width less than 900dp.
197 twoPanedMode = (findViewById<View?>(R.id.domain_settings_fragment_container) != null)
199 // Configure the add domain floating action button.
200 addDomainFAB.setOnClickListener {
201 // Create an add domain dialog.
202 val addDomainDialog: DialogFragment = addDomain(currentUrl)
204 // Show the add domain dialog.
205 addDomainDialog.show(supportFragmentManager, resources.getString(R.string.add_domain))
208 // Get a handle for the activity.
209 val activity: Activity = this
211 // Control what the navigation bar back button does.
212 val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
213 override fun handleOnBackPressed() {
214 if (twoPanedMode) { // The device is in two-paned mode.
215 // Save the current domain settings if the domain settings fragment is displayed.
216 if (findViewById<View?>(R.id.domain_settings_scrollview) != null)
217 saveDomainSettings(coordinatorLayout)
219 // Dismiss the undo delete snackbar if it is shown.
220 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
221 // Set the close flag.
222 closeActivityAfterDismissingSnackbar = true
224 // Dismiss the snackbar.
225 undoDeleteSnackbar!!.dismiss()
228 NavUtils.navigateUpFromSameTask(activity)
230 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
231 // Save the current domain settings.
232 saveDomainSettings(coordinatorLayout)
235 NavUtils.navigateUpFromSameTask(activity)
236 } else if (findViewById<View?>(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and domain settings fragment is displayed.
237 // Save the current domain settings.
238 saveDomainSettings(coordinatorLayout)
240 // Instantiate a new domains list fragment.
241 val domainsListFragment = DomainsListFragment()
243 // Display the domains list fragment.
244 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
246 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
247 populateDomainsListView(-1, domainsListViewPosition)
249 // Show the add domain floating action button.
252 // Hide the delete menu item.
253 deleteMenuItem.isVisible = false
254 } else { // The device is in single-paned mode and the domain list fragment is displayed.
255 // Dismiss the undo delete SnackBar if it is shown.
256 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
257 // Set the close flag.
258 closeActivityAfterDismissingSnackbar = true
260 // Dismiss the snackbar.
261 undoDeleteSnackbar!!.dismiss()
264 NavUtils.navigateUpFromSameTask(activity)
270 // Register the on back pressed callback.
271 onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
274 override fun onCreateOptionsMenu(menu: Menu): Boolean {
276 menuInflater.inflate(R.menu.domains_options_menu, menu)
278 // Get a handle for the delete menu item.
279 deleteMenuItem = menu.findItem(R.id.delete_domain)
281 // Only display the delete menu item (initially) in two-paned mode.
282 deleteMenuItem.isVisible = twoPanedMode
284 // Display the fragments. This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated.
285 if (appRestarted && domainSettingsDisplayedBeforeRestart) { // The app was restarted, possibly because the device was rotated, and domain settings were displayed previously.
286 // Reset the app restarted flag.
289 if (twoPanedMode) { // The device is in two-paned mode.
290 // Initialize the domains list fragment.
291 val domainsListFragment = DomainsListFragment()
293 // Display the domains list fragment.
294 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
296 // Populate the list of domains and highlight the domain that was highlighted before the restart.
297 populateDomainsListView(domainSettingsDatabaseIdBeforeRestart, domainsListViewPosition)
298 } else { // The device is in single-paned mode.
299 // Store the current domain database ID.
300 currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRestart
302 // Create an arguments bundle.
303 val argumentsBundle = Bundle()
305 // Add the domain settings arguments.
306 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
307 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
309 // Instantiate a new domain settings fragment.
310 val domainSettingsFragment = DomainSettingsFragment()
312 // Add the arguments bundle to the domain settings fragment.
313 domainSettingsFragment.arguments = argumentsBundle
315 // Show the delete menu item.
316 deleteMenuItem.isVisible = true
318 // Hide the add domain floating action button.
321 // Display the domain settings fragment.
322 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
324 } else { // The device was not restarted or, if it was, domain settings were not displayed previously.
325 if (goDirectlyToDatabaseId >= 0) { // Load the indicated domain settings.
326 // Store the current domain database ID.
327 currentDomainDatabaseId = goDirectlyToDatabaseId
329 // Check if the device is in two-paned mode.
330 if (twoPanedMode) { // The device is in two-paned mode.
331 // Instantiate a new domains list fragment.
332 val domainsListFragment = DomainsListFragment()
334 // Display the domains list fragment.
335 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
337 // Populate the list of domains.
338 populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition)
339 } else { // The device is in single-paned mode.
340 // Create an arguments bundle.
341 val argumentsBundle = Bundle()
343 // Add the domain settings to arguments bundle.
344 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId)
345 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
347 // Instantiate a new domain settings fragment.
348 val domainSettingsFragment = DomainSettingsFragment()
350 // Add the arguments bundle to the domain settings fragment.
351 domainSettingsFragment.arguments = argumentsBundle
353 // Show the delete menu item.
354 deleteMenuItem.isVisible = true
356 // Hide the add domain floating action button.
359 // Display the domain settings fragment.
360 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
362 } else { // Highlight the first domain.
363 // Instantiate a new domains list fragment.
364 val domainsListFragment = DomainsListFragment()
366 // Display the domain list fragment.
367 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
369 // Populate the list of domains. `-1` highlights the first domain.
370 populateDomainsListView(-1, domainsListViewPosition)
378 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
379 // Run the command according to the selected menu item.
380 when (menuItem.itemId) {
381 android.R.id.home -> { // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
382 // Check if the device is in two-paned mode.
383 if (twoPanedMode) { // The device is in two-paned mode.
384 // Save the current domain settings if the domain settings fragment is displayed.
385 if (findViewById<View?>(R.id.domain_settings_scrollview) != null)
386 saveDomainSettings(coordinatorLayout)
388 // Dismiss the undo delete snackbar if it is shown.
389 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
390 // Set the close flag.
391 closeActivityAfterDismissingSnackbar = true
393 // Dismiss the snackbar.
394 undoDeleteSnackbar!!.dismiss()
397 NavUtils.navigateUpFromSameTask(this)
399 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
400 // Save the current domain settings.
401 saveDomainSettings(coordinatorLayout)
404 NavUtils.navigateUpFromSameTask(this)
405 } else if (findViewById<View?>(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed.
406 // Save the current domain settings.
407 saveDomainSettings(coordinatorLayout)
409 // Instantiate a new domains list fragment.
410 val domainsListFragment = DomainsListFragment()
412 // Display the domains list fragment.
413 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
415 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
416 populateDomainsListView(-1, domainsListViewPosition)
418 // Show the add domain floating action button.
421 // Hide the delete menu item.
422 deleteMenuItem.isVisible = false
423 } else { // The device is in single-paned mode and domains list fragment is displayed.
424 // Dismiss the undo delete snackbar if it is shown.
425 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) {
426 // Set the close flag.
427 closeActivityAfterDismissingSnackbar = true
429 // Dismiss the snackbar.
430 undoDeleteSnackbar!!.dismiss()
433 NavUtils.navigateUpFromSameTask(this)
438 R.id.delete_domain -> { // Delete.
439 // Get a handle for the activity (used in an inner class below).
440 val activity: Activity = this
442 // Check to see if the domain settings were loaded directly for editing of this app in single-paned mode.
443 if (closeOnBack && !twoPanedMode) { // The activity should delete the domain settings and exit straight to the the main WebView activity.
444 // Delete the selected domain.
445 domainsDatabaseHelper.deleteDomain(currentDomainDatabaseId)
448 NavUtils.navigateUpFromSameTask(activity)
449 } else { // A snackbar should be shown before deleting the domain settings.
450 // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists.
453 // Store a copy of the current domain database ID because it could change while the snackbar is displayed.
454 val databaseIdToDelete = currentDomainDatabaseId
456 // Update the fragments and menu items.
457 if (twoPanedMode) { // Two-paned mode.
458 // Store the deleted domain position, which is needed if undo is selected in the snackbar.
459 deletedDomainPosition = domainsListView!!.checkedItemPosition
461 // Disable the delete menu item.
462 deleteMenuItem.isEnabled = false
464 // Remove the domain settings fragment.
465 supportFragmentManager.beginTransaction().remove(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!!).commitNow()
466 } else { // Single-paned mode.
467 // Instantiate a new domains list fragment.
468 val domainsListFragment = DomainsListFragment()
470 // Display the domains list fragment.
471 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow()
473 // Show the add domain floating action button.
476 // Hide the delete menu item.
477 deleteMenuItem.isVisible = false
480 // Get a cursor that does not show the domain to be deleted.
481 val domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete)
483 // Setup the domains pending delete cursor adapter.
484 val domainsPendingDeleteCursorAdapter: CursorAdapter = object : CursorAdapter(this, domainsPendingDeleteCursor, false) {
485 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
486 // Inflate the individual item layout.
487 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
490 override fun bindView(view: View, context: Context, cursor: Cursor) {
491 // Get the domain name string.
492 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME))
494 // Get a handle for the domain name text view.
495 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
497 // Display the domain name.
498 domainNameTextView.text = domainNameString
502 // Update the handle for the current domains list view.
503 domainsListView = findViewById(R.id.domains_listview)
505 // Update the list view.
506 domainsListView!!.adapter = domainsPendingDeleteCursorAdapter
508 // Display a snackbar.
509 undoDeleteSnackbar = Snackbar.make(domainsListView!!, R.string.domain_deleted, Snackbar.LENGTH_LONG)
510 .setAction(R.string.undo) {} // Do nothing because everything will be handled by `onDismissed()` below.
511 .addCallback(object : Snackbar.Callback() {
512 override fun onDismissed(snackbar: Snackbar, event: Int) {
513 // Run commands based on the event.
514 if (event == DISMISS_EVENT_ACTION) { // The user pushed the `Undo` button.
515 // Create an arguments bundle.
516 val argumentsBundle = Bundle()
518 // Store the domains settings in the arguments bundle.
519 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete)
520 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY)
522 // Instantiate a new domain settings fragment.
523 val domainSettingsFragment = DomainSettingsFragment()
525 // Add the arguments bundle to the domain settings fragment.
526 domainSettingsFragment.arguments = argumentsBundle
528 // Display the correct fragments.
529 if (twoPanedMode) { // The device in in two-paned mode.
530 // Get a cursor with the current contents of the domains database.
531 val undoDeleteDomainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain
533 // Setup the domains cursor adapter.
534 val undoDeleteDomainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, undoDeleteDomainsCursor, false) {
535 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
536 // Inflate the individual item layout.
537 return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false)
540 override fun bindView(view: View, context: Context, cursor: Cursor) {
541 /// Get the domain name string.
542 val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME))
544 // Get a handle for the domain name text view.
545 val domainNameTextView = view.findViewById<TextView>(R.id.domain_name_textview)
547 // Display the domain name.
548 domainNameTextView.text = domainNameString
552 // Update the domains list view.
553 domainsListView!!.adapter = undoDeleteDomainsCursorAdapter
555 // Select the previously deleted domain in the list view.
556 domainsListView!!.setItemChecked(deletedDomainPosition, true)
558 // Display the domain settings fragment.
559 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commitNow()
561 // Enable the delete menu item.
562 deleteMenuItem.isEnabled = true
563 } else { // The device in in one-paned mode.
564 // Display the domain settings fragment.
565 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
567 // Hide the add domain floating action button.
570 // Show and enable the delete menu item.
571 deleteMenuItem.isVisible = true
573 // Display the domain settings fragment.
574 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
576 } else { // The snackbar was dismissed without the undo button being pushed.
577 // Delete the selected domain.
578 domainsDatabaseHelper.deleteDomain(databaseIdToDelete)
580 // Enable the delete menu item if the system was waiting for a snackbar to be dismissed.
581 if (dismissingSnackbar) {
582 // Create a runnable to enable the delete menu item.
583 val enableDeleteMenuItemRunnable = Runnable {
584 // Enable or show the delete menu item according to the display mode.
586 deleteMenuItem.isEnabled = true
588 deleteMenuItem.isVisible = true
590 // Reset the dismissing snackbar tracker.
591 dismissingSnackbar = false
594 // Instantiate a handler running the main looper.
595 val handler = Handler(mainLooper)
597 // Enable or show the delete menu icon after 100 milliseconds to make sure that the previous domain has been deleted from the database.
598 handler.postDelayed(enableDeleteMenuItemRunnable, 100)
601 // Close the activity if back was pressed.
602 if (closeActivityAfterDismissingSnackbar)
603 NavUtils.navigateUpFromSameTask(activity)
608 // Show the Snackbar.
609 undoDeleteSnackbar!!.show()
614 // Consume the event.
618 override fun onSaveInstanceState(savedInstanceState: Bundle) {
619 // Run the default commands.
620 super.onSaveInstanceState(savedInstanceState)
622 // Get a handle for the domain settings scrollview.
623 val domainSettingsScrollView = findViewById<ScrollView>(R.id.domain_settings_scrollview)
625 // Check to see if the domain settings scrollview exists.
626 if (domainSettingsScrollView == null) { // The domain settings are not displayed.
627 // Store the domain settings status in the bundle.
628 savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false)
629 savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1)
630 savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0)
631 } else { // The domain settings are displayed.
632 // Save any changes that have been made to the domain settings.
633 saveDomainSettings(coordinatorLayout)
635 // Get the domain settings scroll Y.
636 val domainSettingsScrollY = domainSettingsScrollView.scrollY
638 // Store the domain settings status in the bundle.
639 savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true)
640 savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId)
641 savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY)
644 // Check to see if the domains listview exists.
645 if (domainsListView != null) {
646 // Get the domains listview position.
647 val domainsListViewPosition = domainsListView!!.firstVisiblePosition
649 // Store the listview position in the bundle.
650 savedInstanceState.putInt(LISTVIEW_POSITION, domainsListViewPosition)
654 override fun onAddDomain(dialogFragment: DialogFragment) {
655 // Dismiss the undo delete snackbar if it is currently displayed.
656 if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown)
657 undoDeleteSnackbar!!.dismiss()
659 // Get a handle for the domain name edit text.
660 val domainNameEditText = dialogFragment.dialog!!.findViewById<EditText>(R.id.domain_name_edittext)
662 // Get the domain name string.
663 val domainNameString = domainNameEditText.text.toString()
665 // Create the domain and store the database ID in the current domain database ID.
666 currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString)
668 // Display the newly created domain.
669 if (twoPanedMode) { // The device in in two-paned mode.
670 populateDomainsListView(currentDomainDatabaseId, 0)
671 } else { // The device is in single-paned mode.
672 // Hide the add domain floating action button.
675 // Show and enable the delete menu item.
676 deleteMenuItem.isVisible = true
678 // Create an arguments bundle.
679 val argumentsBundle = Bundle()
681 // Add the domain settings to the arguments bundle. The scroll Y should always be `0` on a new domain.
682 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId)
683 argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, 0)
685 // Instantiate a new domain settings fragment.
686 val domainSettingsFragment = DomainSettingsFragment()
688 // Add the arguments bundle to the domain setting fragment.
689 domainSettingsFragment.arguments = argumentsBundle
691 // Display the domain settings fragment.
692 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow()
696 override fun saveDomainSettings(view: View) {
697 // Get handles for the domain settings.
698 val domainNameEditText = view.findViewById<EditText>(R.id.domain_settings_name_edittext)
699 val javaScriptSwitch = view.findViewById<SwitchCompat>(R.id.javascript_switch)
700 val cookiesSwitch = view.findViewById<SwitchCompat>(R.id.cookies_switch)
701 val domStorageSwitch = view.findViewById<SwitchCompat>(R.id.dom_storage_switch)
702 val formDataSwitch = view.findViewById<SwitchCompat>(R.id.form_data_switch) // Form data can be removed once the minimum API >= 26.
703 val easyListSwitch = view.findViewById<SwitchCompat>(R.id.easylist_switch)
704 val easyPrivacySwitch = view.findViewById<SwitchCompat>(R.id.easyprivacy_switch)
705 val fanboysAnnoyanceSwitch = view.findViewById<SwitchCompat>(R.id.fanboys_annoyance_list_switch)
706 val fanboysSocialBlockingSwitch = view.findViewById<SwitchCompat>(R.id.fanboys_social_blocking_list_switch)
707 val ultraListSwitch = view.findViewById<SwitchCompat>(R.id.ultralist_switch)
708 val ultraPrivacySwitch = view.findViewById<SwitchCompat>(R.id.ultraprivacy_switch)
709 val blockAllThirdPartyRequestsSwitch = view.findViewById<SwitchCompat>(R.id.block_all_third_party_requests_switch)
710 val userAgentSpinner = view.findViewById<Spinner>(R.id.user_agent_spinner)
711 val customUserAgentEditText = view.findViewById<EditText>(R.id.custom_user_agent_edittext)
712 val xRequestedWithHeaderSpinner = view.findViewById<Spinner>(R.id.x_requested_with_header_spinner)
713 val fontSizeSpinner = view.findViewById<Spinner>(R.id.font_size_spinner)
714 val customFontSizeEditText = view.findViewById<EditText>(R.id.custom_font_size_edittext)
715 val swipeToRefreshSpinner = view.findViewById<Spinner>(R.id.swipe_to_refresh_spinner)
716 val webViewThemeSpinner = view.findViewById<Spinner>(R.id.webview_theme_spinner)
717 val wideViewportSpinner = view.findViewById<Spinner>(R.id.wide_viewport_spinner)
718 val displayWebpageImagesSpinner = view.findViewById<Spinner>(R.id.display_webpage_images_spinner)
719 val pinnedSslCertificateSwitch = view.findViewById<SwitchCompat>(R.id.pinned_ssl_certificate_switch)
720 val currentWebsiteCertificateRadioButton = view.findViewById<RadioButton>(R.id.current_website_certificate_radiobutton)
721 val pinnedIpAddressesSwitch = view.findViewById<SwitchCompat>(R.id.pinned_ip_addresses_switch)
722 val currentIpAddressesRadioButton = view.findViewById<RadioButton>(R.id.current_ip_addresses_radiobutton)
724 // Extract the data for the domain settings.
725 val domainNameString = domainNameEditText.text.toString()
726 val javaScript = javaScriptSwitch.isChecked
727 val cookies = cookiesSwitch.isChecked
728 val domStorage = domStorageSwitch.isChecked
729 val formData = formDataSwitch.isChecked // Form data can be removed once the minimum API >= 26.
730 val easyList = easyListSwitch.isChecked
731 val easyPrivacy = easyPrivacySwitch.isChecked
732 val fanboysAnnoyance = fanboysAnnoyanceSwitch.isChecked
733 val fanboysSocialBlocking = fanboysSocialBlockingSwitch.isChecked
734 val ultraList = ultraListSwitch.isChecked
735 val ultraPrivacy = ultraPrivacySwitch.isChecked
736 val blockAllThirdPartyRequests = blockAllThirdPartyRequestsSwitch.isChecked
737 val userAgentSwitchPosition = userAgentSpinner.selectedItemPosition
738 val xRequestedWithHeaderSwitchInt = xRequestedWithHeaderSpinner.selectedItemPosition
739 val fontSizeSwitchPosition = fontSizeSpinner.selectedItemPosition
740 val swipeToRefreshInt = swipeToRefreshSpinner.selectedItemPosition
741 val webViewThemeInt = webViewThemeSpinner.selectedItemPosition
742 val wideViewportInt = wideViewportSpinner.selectedItemPosition
743 val displayWebpageImagesInt = displayWebpageImagesSpinner.selectedItemPosition
744 val pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked
745 val pinnedIpAddress = pinnedIpAddressesSwitch.isChecked
747 // Get the user agent name.
748 val userAgentName: String = when (userAgentSwitchPosition) {
749 MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT -> resources.getString(R.string.system_default_user_agent) // Set the user agent name to be `System default user agent`.
750 MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT -> customUserAgentEditText.text.toString() // Set the user agent name to be the custom user agent.
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, javaScript, cookies, domStorage, formData, easyList, easyPrivacy, fanboysAnnoyance, fanboysSocialBlocking, ultraList,
769 ultraPrivacy, blockAllThirdPartyRequests, userAgentName, xRequestedWithHeaderSwitchInt, 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(DomainsDatabaseHelper.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(DomainsDatabaseHelper.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(DomainsDatabaseHelper.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.