From a156c3942ca31a1afca3271245cc2bda7ed5aed8 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Mon, 24 Oct 2022 14:14:43 -0700 Subject: [PATCH] Fix crash when adding domain settings with null domain. https://redmine.stoutner.com/issues/906 --- app/build.gradle | 3 +- app/src/main/AndroidManifest.xml | 1 + .../activities/AboutActivity.kt | 5 +- .../activities/DomainsActivity.java | 938 --------- .../activities/DomainsActivity.kt | 880 ++++++++ .../activities/GuideActivity.java | 94 - .../activities/GuideActivity.kt | 85 + .../activities/MainWebViewActivity.java | 89 +- .../adapters/GuidePagerAdapter.java | 93 - .../adapters/GuidePagerAdapter.kt | 57 + .../privacybrowser/dialogs/AddDomainDialog.kt | 4 +- .../fragments/AboutVersionFragment.kt | 16 +- .../fragments/AboutWebViewFragment.kt | 4 +- .../fragments/DomainSettingsFragment.java | 1768 ----------------- .../fragments/DomainSettingsFragment.kt | 1610 +++++++++++++++ .../fragments/DomainsListFragment.java | 139 -- .../fragments/DomainsListFragment.kt | 136 ++ .../fragments/GuideWebViewFragment.java | 206 -- .../fragments/GuideWebViewFragment.kt | 162 ++ .../fragments/SettingsFragment.java | 42 +- .../main/res/color/black_icon_selector.xml | 25 + .../{delete_disabled.xml => delete.xml} | 2 +- app/src/main/res/drawable/delete_enabled.xml | 13 - .../main/res/menu/bookmarks_context_menu.xml | 2 +- .../bookmarks_databaseview_context_menu.xml | 2 +- .../main/res/menu/domains_options_menu.xml | 2 +- app/src/main/res/values-night/colors.xml | 1 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 9 +- app/src/main/res/xml/preferences.xml | 14 +- build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 32 files changed, 3063 insertions(+), 3346 deletions(-) delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/fragments/GuideWebViewFragment.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/fragments/GuideWebViewFragment.kt create mode 100644 app/src/main/res/color/black_icon_selector.xml rename app/src/main/res/drawable/{delete_disabled.xml => delete.xml} (89%) delete mode 100644 app/src/main/res/drawable/delete_enabled.xml diff --git a/app/build.gradle b/app/build.gradle index e9754893..e36ed768 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,6 +65,7 @@ android { dependencies { // Include the following AndroidX libraries. + implementation "androidx.activity:activity-ktx:1.7.0-alpha02" implementation 'androidx.arch.core:core-common:2.1.0' implementation 'androidx.arch.core:core-runtime:2.1.0' implementation 'androidx.appcompat:appcompat:1.5.1' @@ -82,5 +83,5 @@ dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10' // Include the Google material library. - implementation 'com.google.android.material:material:1.6.1' + implementation 'com.google.android.material:material:1.7.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 829c4a1a..0dbae9b6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,6 +57,7 @@ android:supportsRtl="true" android:theme="@style/PrivacyBrowser" android:networkSecurityConfig="@xml/network_security_config" + android:enableOnBackInvokedCallback="true" tools:ignore="DataExtractionRules,UnusedAttribute" > diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.kt index 73f6fc0e..0e1f320d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.kt @@ -33,9 +33,6 @@ import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.adapters.AboutPagerAdapter class AboutActivity : AppCompatActivity() { - // Declare the class variables. - private lateinit var aboutPagerAdapter: AboutPagerAdapter - companion object { // Define the companion object constants. These can be move to being public constants once MainWebViewActivity has been converted to Kotlin. const val BLOCKLIST_VERSIONS = "blocklist_versions" @@ -85,7 +82,7 @@ class AboutActivity : AppCompatActivity() { actionBar.setDisplayHomeAsUpEnabled(true) // Initialize the about pager adapter. - aboutPagerAdapter = AboutPagerAdapter(supportFragmentManager, applicationContext, blocklistVersions) + val aboutPagerAdapter = AboutPagerAdapter(supportFragmentManager, applicationContext, blocklistVersions) // Set the view pager adapter. aboutViewPager.adapter = aboutPagerAdapter diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java deleted file mode 100644 index 3718edbd..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java +++ /dev/null @@ -1,938 +0,0 @@ -/* - * Copyright © 2017-2022 Soren Stoutner . - * - * This file is part of Privacy Browser Android . - * - * Privacy Browser Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privacy Browser Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privacy Browser Android. If not, see . - */ - -package com.stoutner.privacybrowser.activities; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.Cursor; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.CursorAdapter; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.RadioButton; -import android.widget.ScrollView; -import android.widget.Spinner; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.SwitchCompat; -import androidx.appcompat.widget.Toolbar; -import androidx.core.app.NavUtils; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentManager; - -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.Snackbar; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.dialogs.AddDomainDialog; -import com.stoutner.privacybrowser.fragments.DomainSettingsFragment; -import com.stoutner.privacybrowser.fragments.DomainsListFragment; -import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; - -import java.util.Objects; - -public class DomainsActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener, DomainsListFragment.DismissSnackbarInterface { - // Define the public static variables. - public static int domainsListViewPosition; - public static boolean twoPanedMode; - public static int currentDomainDatabaseId; - public static MenuItem deleteMenuItem; - public static boolean dismissingSnackbar; - - // The SSL certificate and IP address information are accessed from `DomainSettingsFragment` and `saveDomainSettings()`. - public static String sslIssuedToCName; - public static String sslIssuedToOName; - public static String sslIssuedToUName; - public static String sslIssuedByCName; - public static String sslIssuedByOName; - public static String sslIssuedByUName; - public static long sslStartDateLong; - public static long sslEndDateLong; - public static String currentIpAddresses; - - - // Initialize the class constants. - private final String LISTVIEW_POSITION = "listview_position"; - private final String DOMAIN_SETTINGS_DISPLAYED = "domain_settings_displayed"; - private final String DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_is"; - private final String DOMAIN_SETTINGS_SCROLL_Y = "domain_settings_scroll_y"; - - // Initialize the class variables. - private boolean restartAfterRotate; - private boolean domainSettingsDisplayedBeforeRotate; - private int domainSettingsDatabaseIdBeforeRotate; - private int domainSettingsScrollY = 0; - - // Defile the class views. - private ListView domainsListView; - - // `closeActivityAfterDismissingSnackbar` is used in `onOptionsItemSelected()`, and `onBackPressed()`. - private boolean closeActivityAfterDismissingSnackbar; - - // The undelete snackbar is used in `onOptionsItemSelected()` and `onBackPressed()`. - private Snackbar undoDeleteSnackbar; - - // `domainsDatabaseHelper` is used in `onCreate()`, `saveDomainSettings()`, and `onDestroy()`. - private static DomainsDatabaseHelper domainsDatabaseHelper; - - // `addDomainFAB` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`. - private FloatingActionButton addDomainFAB; - - // `deletedDomainPosition` is used in an inner and outer class in `onOptionsItemSelected()`. - private int deletedDomainPosition; - - // `goDirectlyToDatabaseId` is used in `onCreate()` and `onCreateOptionsMenu()`. - private int goDirectlyToDatabaseId; - - // `closeOnBack` is used in `onCreate()`, `onOptionsItemSelected()` and `onBackPressed()`. - private boolean closeOnBack; - - // `coordinatorLayout` is use in `onCreate()`, `onOptionsItemSelected()`, and `onSaveInstanceState()`. - private View coordinatorLayout; - - // `resources` is used in `onCreate()`, `onOptionsItemSelected()`, and `onSaveInstanceState()`. - private Resources resources; - - @Override - protected void onCreate(Bundle savedInstanceState) { - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Get the preferences. - boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false); - boolean bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - // Run the default commands. - super.onCreate(savedInstanceState); - - // Initialize the domains listview position. - domainsListViewPosition = 0; - - // Extract the values from the saved instance state if it is not null. - if (savedInstanceState != null) { - domainsListViewPosition = savedInstanceState.getInt(LISTVIEW_POSITION); - restartAfterRotate = true; - domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean(DOMAIN_SETTINGS_DISPLAYED); - domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt(DOMAIN_SETTINGS_DATABASE_ID); - domainSettingsScrollY = savedInstanceState.getInt(DOMAIN_SETTINGS_SCROLL_Y); - } - - // Get the launching intent - Intent intent = getIntent(); - - // Extract the domain to load if there is one. `-1` is the default value. - goDirectlyToDatabaseId = intent.getIntExtra("load_domain", -1); - - // Get the status of close-on-back, which is true when the domains activity is called from the options menu. - closeOnBack = intent.getBooleanExtra("close_on_back", false); - - // Get the current URL. - String currentUrl = intent.getStringExtra("current_url"); - - // Store the current SSL certificate information in class variables. - sslIssuedToCName = intent.getStringExtra("ssl_issued_to_cname"); - sslIssuedToOName = intent.getStringExtra("ssl_issued_to_oname"); - sslIssuedToUName = intent.getStringExtra("ssl_issued_to_uname"); - sslIssuedByCName = intent.getStringExtra("ssl_issued_by_cname"); - sslIssuedByOName = intent.getStringExtra("ssl_issued_by_oname"); - sslIssuedByUName = intent.getStringExtra("ssl_issued_by_uname"); - sslStartDateLong = intent.getLongExtra("ssl_start_date", 0); - sslEndDateLong = intent.getLongExtra("ssl_end_date", 0); - currentIpAddresses = intent.getStringExtra("current_ip_addresses"); - - // Set the view. - if (bottomAppBar) { - setContentView(R.layout.domains_bottom_appbar); - } else { - setContentView(R.layout.domains_top_appbar); - } - - // Populate the class variables. - coordinatorLayout = findViewById(R.id.domains_coordinatorlayout); - resources = getResources(); - - // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21. - final Toolbar toolbar = findViewById(R.id.domains_toolbar); - setSupportActionBar(toolbar); - - // Get a handle for the action bar. - ActionBar actionBar = getSupportActionBar(); - - // Remove the incorrect lint warning that the action bar might be null. - assert actionBar != null; - - // Set the back arrow on the action bar. - actionBar.setDisplayHomeAsUpEnabled(true); - - // Initialize the database handler. - domainsDatabaseHelper = new DomainsDatabaseHelper(this); - - // Determine if we are in two pane mode. `domain_settings_fragment_container` does not exist on devices with a width less than 900dp. - twoPanedMode = (findViewById(R.id.domain_settings_fragment_container) != null); - - // Get a handle for the add domain floating action button. - addDomainFAB = findViewById(R.id.add_domain_fab); - - // Configure the add domain floating action button. - addDomainFAB.setOnClickListener((View view) -> { - // Remove the incorrect warning below that the current URL might be null. - assert currentUrl != null; - - // Create an add domain dialog. - DialogFragment addDomainDialog = AddDomainDialog.addDomain(currentUrl); - - // Show the add domain dialog. - addDomainDialog.show(getSupportFragmentManager(), resources.getString(R.string.add_domain)); - }); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu. - getMenuInflater().inflate(R.menu.domains_options_menu, menu); - - // Store `deleteMenuItem` for future use. - deleteMenuItem = menu.findItem(R.id.delete_domain); - - // Only display `deleteMenuItem` (initially) in two-paned mode. - deleteMenuItem.setVisible(twoPanedMode); - - // Get a handle for the fragment manager. - FragmentManager fragmentManager = getSupportFragmentManager(); - - // Display the fragments. This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated. - if (restartAfterRotate && domainSettingsDisplayedBeforeRotate) { // The device was rotated and domain settings were displayed previously. - if (twoPanedMode) { // The device is in two-paned mode. - // Reset `restartAfterRotate`. - restartAfterRotate = false; - - // Display `DomainsListFragment`. - DomainsListFragment domainsListFragment = new DomainsListFragment(); - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit(); - fragmentManager.executePendingTransactions(); - - // Populate the list of domains. `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation. - populateDomainsListView(domainSettingsDatabaseIdBeforeRotate, domainsListViewPosition); - } else { // The device is in single-paned mode. - // Reset `restartAfterRotate`. - restartAfterRotate = false; - - // Store the current domain database ID. - currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRotate; - - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Add the domain settings arguments. - argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId); - argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY); - - // Instantiate a new domain settings fragment. - DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment(); - - // Add the arguments bundle to the domain settings fragment. - domainSettingsFragment.setArguments(argumentsBundle); - - // Show the delete menu item. - deleteMenuItem.setVisible(true); - - // Hide the add domain floating action button. - addDomainFAB.hide(); - - // Display the domain settings fragment. - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit(); - } - } else { // The device was not rotated or, if it was, domain settings were not displayed previously. - if (goDirectlyToDatabaseId >=0) { // Load the indicated domain settings. - // Store the current domain database ID. - currentDomainDatabaseId = goDirectlyToDatabaseId; - - if (twoPanedMode) { // The device is in two-paned mode. - // Display `DomainsListFragment`. - DomainsListFragment domainsListFragment = new DomainsListFragment(); - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit(); - fragmentManager.executePendingTransactions(); - - // Populate the list of domains. `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation. - populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition); - } else { // The device is in single-paned mode. - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Add the domain settings to arguments bundle. - argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId); - argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY); - - // Instantiate a new domain settings fragment. - DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment(); - - // Add the arguments bundle to the domain settings fragment`. - domainSettingsFragment.setArguments(argumentsBundle); - - // Show the delete menu item. - deleteMenuItem.setVisible(true); - - // Hide the add domain floating action button. - addDomainFAB.hide(); - - // Display the domain settings fragment. - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit(); - } - } else { // Highlight the first domain. - // Display `DomainsListFragment`. - DomainsListFragment domainsListFragment = new DomainsListFragment(); - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit(); - fragmentManager.executePendingTransactions(); - - // Populate the list of domains. `-1` highlights the first domain. - populateDomainsListView(-1, domainsListViewPosition); - } - } - - // Success! - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - // Get the ID of the menu item that was selected. - int menuItemId = menuItem.getItemId(); - - // Get a handle for the fragment manager. - FragmentManager fragmentManager = getSupportFragmentManager(); - - // Run the command according to the selected menu item. - if (menuItemId == android.R.id.home) { // The home arrow is identified as `android.R.id.home`, not just `R.id.home`. - // Check if the device is in two-paned mode. - if (twoPanedMode) { // The device is in two-paned mode. - // Save the current domain settings if the domain settings fragment is displayed. - if (findViewById(R.id.domain_settings_scrollview) != null) { - saveDomainSettings(coordinatorLayout, resources); - } - - // Dismiss the undo delete snackbar if it is shown. - if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) { - // Set the close flag. - closeActivityAfterDismissingSnackbar = true; - - // Dismiss the snackbar. - undoDeleteSnackbar.dismiss(); - } else { - // Go home. - NavUtils.navigateUpFromSameTask(this); - } - } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu. - // Save the current domain settings. - saveDomainSettings(coordinatorLayout, resources); - - // Go home. - NavUtils.navigateUpFromSameTask(this); - } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed. - // Save the current domain settings. - saveDomainSettings(coordinatorLayout, resources); - - // Display the domains list fragment. - DomainsListFragment domainsListFragment = new DomainsListFragment(); - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit(); - fragmentManager.executePendingTransactions(); - - // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode. - populateDomainsListView(-1, domainsListViewPosition); - - // Show the add domain floating action button. - addDomainFAB.show(); - - // Hide the delete menu item. - deleteMenuItem.setVisible(false); - } else { // The device is in single-paned mode and domains list fragment is displayed. - // Dismiss the undo delete `SnackBar` if it is shown. - if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) { - // Set the close flag. - closeActivityAfterDismissingSnackbar = true; - - // Dismiss the snackbar. - undoDeleteSnackbar.dismiss(); - } else { - // Go home. - NavUtils.navigateUpFromSameTask(this); - } - } - } else if (menuItemId == R.id.delete_domain) { // Delete. - // Get a handle for the activity. - Activity activity = this; - - // Check to see if the domain settings were loaded directly for editing of this app in single-paned mode. - if (closeOnBack && !twoPanedMode) { // The activity should delete the domain settings and exit straight to the the main WebView activity. - // Delete the selected domain. - domainsDatabaseHelper.deleteDomain(currentDomainDatabaseId); - - // Go home. - NavUtils.navigateUpFromSameTask(activity); - } else { // A snackbar should be shown before deleting the domain settings. - // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists. - closeOnBack = false; - - // Store a copy of the current domain database ID because it could change while the snackbar is displayed. - final int databaseIdToDelete = currentDomainDatabaseId; - - // Update the fragments and menu items. - if (twoPanedMode) { // Two-paned mode. - // Store the deleted domain position, which is needed if undo is selected in the snackbar. - deletedDomainPosition = domainsListView.getCheckedItemPosition(); - - // Disable the options menu items. - deleteMenuItem.setEnabled(false); - deleteMenuItem.setIcon(R.drawable.delete_disabled); - - // Remove the domain settings fragment. - fragmentManager.beginTransaction().remove(Objects.requireNonNull(fragmentManager.findFragmentById(R.id.domain_settings_fragment_container))).commit(); - } else { // Single-paned mode. - // Display the domains list fragment. - DomainsListFragment domainsListFragment = new DomainsListFragment(); - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit(); - fragmentManager.executePendingTransactions(); - - // Show the add domain floating action button. - addDomainFAB.show(); - - // Hide `deleteMenuItem`. - deleteMenuItem.setVisible(false); - } - - // Get a cursor that does not show the domain to be deleted. - Cursor domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete); - - // Setup the domains pending delete cursor adapter. - CursorAdapter domainsPendingDeleteCursorAdapter = new CursorAdapter(this, domainsPendingDeleteCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. - return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Get the domain name string. - String domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)); - - // Get a handle for the domain name text view. - TextView domainNameTextView = view.findViewById(R.id.domain_name_textview); - - // Display the domain name. - domainNameTextView.setText(domainNameString); - } - }; - - // Update the handle for the current domains list view. - domainsListView = findViewById(R.id.domains_listview); - - // Update the list view. - domainsListView.setAdapter(domainsPendingDeleteCursorAdapter); - - // Display a snackbar. - undoDeleteSnackbar = Snackbar.make(domainsListView, R.string.domain_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, (View v) -> { - // Do nothing because everything will be handled by `onDismissed()` below. - }) - .addCallback(new Snackbar.Callback() { - @Override - public void onDismissed(Snackbar snackbar, int event) { - // Run commands based on the event. - if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // The user pushed the `Undo` button. - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Store the domains settings in the arguments bundle. - argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete); - argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY); - - // Instantiate a new domain settings fragment. - DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment(); - - // Add the arguments bundle to the domain settings fragment. - domainSettingsFragment.setArguments(argumentsBundle); - - // Display the correct fragments. - if (twoPanedMode) { // The device in in two-paned mode. - // Get a cursor with the current contents of the domains database. - Cursor undoDeleteDomainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain(); - - // Setup the domains cursor adapter. - CursorAdapter undoDeleteDomainsCursorAdapter = new CursorAdapter(getApplicationContext(), undoDeleteDomainsCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. - return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - /// Get the domain name string. - String domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)); - - // Get a handle for the domain name text view. - TextView domainNameTextView = view.findViewById(R.id.domain_name_textview); - - // Display the domain name. - domainNameTextView.setText(domainNameString); - } - }; - - // Update the domains list view. - domainsListView.setAdapter(undoDeleteDomainsCursorAdapter); - - // Select the previously deleted domain in the list view. - domainsListView.setItemChecked(deletedDomainPosition, true); - - // Display the domain settings fragment. - fragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit(); - - // Enable the options delete menu item. - deleteMenuItem.setEnabled(true); - - // Set the delete menu item icon. - deleteMenuItem.setIcon(R.drawable.delete_enabled); - } else { // The device in in one-paned mode. - // Display the domain settings fragment. - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit(); - - // Hide the add domain floating action button. - addDomainFAB.hide(); - - // Show and enable the delete menu item. - deleteMenuItem.setVisible(true); - - // Display the domain settings fragment. - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit(); - } - } else { // The snackbar was dismissed without the undo button being pushed. - // Delete the selected domain. - domainsDatabaseHelper.deleteDomain(databaseIdToDelete); - - // Enable the delete menu item if the system was waiting for a snackbar to be dismissed. - if (dismissingSnackbar) { - // Create a `Runnable` to enable the delete menu item. - Runnable enableDeleteMenuItemRunnable = () -> { - // Enable the delete menu item according to the display mode. - if (twoPanedMode) { // Two-paned mode. - // Enable the delete menu item. - deleteMenuItem.setEnabled(true); - - // Set the delete menu item icon. - deleteMenuItem.setIcon(R.drawable.delete_enabled); - } else { // Single-paned mode. - // Show the delete menu item. - deleteMenuItem.setVisible(true); - } - - // Reset the dismissing snackbar tracker. - dismissingSnackbar = false; - }; - - // Enable the delete menu icon after 100 milliseconds to make sure that the previous domain has been deleted from the database. - Handler handler = new Handler(); - handler.postDelayed(enableDeleteMenuItemRunnable, 100); - } - - // Close the activity if back was pressed. - if (closeActivityAfterDismissingSnackbar) { - // Go home. - NavUtils.navigateUpFromSameTask(activity); - } - } - } - }); - - // Show the Snackbar. - undoDeleteSnackbar.show(); - } - } - - // Consume the event. - return true; - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) { - // Run the default commands. - super.onSaveInstanceState(savedInstanceState); - - // Get a handle for the domain settings scrollview. - ScrollView domainSettingsScrollView = findViewById(R.id.domain_settings_scrollview); - - // Check to see if the domain settings scrollview exists. - if (domainSettingsScrollView == null) { // The domain settings are not displayed. - // Store the domain settings status in the bundle. - savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false); - savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1); - savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0); - } else { // The domain settings are displayed. - // Save any changes that have been made to the domain settings. - saveDomainSettings(coordinatorLayout, resources); - - // Get the domain settings scroll Y. - int domainSettingsScrollY = domainSettingsScrollView.getScrollY(); - - // Store the domain settings status in the bundle. - savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true); - savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId); - savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY); - } - - // Check to see if the domains listview exists. - if (domainsListView != null) { - // Get the domains listview position. - int domainsListViewPosition = domainsListView.getFirstVisiblePosition(); - - // Store the listview position in the bundle. - savedInstanceState.putInt(LISTVIEW_POSITION, domainsListViewPosition); - } - } - - // Control what the navigation bar back button does. - @Override - public void onBackPressed() { - // Get a handle for the fragment manager. - FragmentManager fragmentManager = getSupportFragmentManager(); - - if (twoPanedMode) { // The device is in two-paned mode. - // Save the current domain settings if the domain settings fragment is displayed. - if (findViewById(R.id.domain_settings_scrollview) != null) { - saveDomainSettings(coordinatorLayout, resources); - } - - // Dismiss the undo delete SnackBar if it is shown. - if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) { - // Set the close flag. - closeActivityAfterDismissingSnackbar = true; - - // Dismiss the snackbar. - undoDeleteSnackbar.dismiss(); - } else { - // Pass `onBackPressed()` to the system. - super.onBackPressed(); - } - } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu. - // Save the current domain settings. - saveDomainSettings(coordinatorLayout, resources); - - // Pass `onBackPressed()` to the system. - super.onBackPressed(); - } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and domain settings fragment is displayed. - // Save the current domain settings. - saveDomainSettings(coordinatorLayout, resources); - - // Display the domains list fragment. - DomainsListFragment domainsListFragment = new DomainsListFragment(); - fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit(); - fragmentManager.executePendingTransactions(); - - // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode. - populateDomainsListView(-1, domainsListViewPosition); - - // Show the add domain floating action button. - addDomainFAB.show(); - - // Hide the delete menu item. - deleteMenuItem.setVisible(false); - } else { // The device is in single-paned mode and the domain list fragment is displayed. - // Dismiss the undo delete SnackBar if it is shown. - if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) { - // Set the close flag. - closeActivityAfterDismissingSnackbar = true; - - // Dismiss the snackbar. - undoDeleteSnackbar.dismiss(); - } else { - // Pass `onBackPressed()` to the system. - super.onBackPressed(); - } - } - } - - @Override - public void onAddDomain(@NonNull DialogFragment dialogFragment) { - // Dismiss the undo delete snackbar if it is currently displayed. - if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) { - undoDeleteSnackbar.dismiss(); - } - - // Remove the incorrect lint warning below that the dialog might be null. - assert dialogFragment.getDialog() != null; - - // Get a handle for the domain name edit text. - EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext); - - // Get the domain name string. - String domainNameString = domainNameEditText.getText().toString(); - - // Create the domain and store the database ID in `currentDomainDatabaseId`. - currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString); - - // Display the newly created domain. - if (twoPanedMode) { // The device in in two-paned mode. - populateDomainsListView(currentDomainDatabaseId, 0); - } else { // The device is in single-paned mode. - // Hide the add domain floating action button. - addDomainFAB.hide(); - - // Show and enable the delete menu item. - DomainsActivity.deleteMenuItem.setVisible(true); - - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Add the domain settings to the arguments bundle. The scroll Y should always be `0` on a new domain. - argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId); - argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, 0); - - // Instantiate a new domain settings fragment. - DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment(); - - // Add the arguments bundle to the domain setting fragment. - domainSettingsFragment.setArguments(argumentsBundle); - - // Display the domain settings fragment. - getSupportFragmentManager().beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit(); - } - } - - public void saveDomainSettings(View view, Resources resources) { - // Get handles for the domain settings. - EditText domainNameEditText = view.findViewById(R.id.domain_settings_name_edittext); - SwitchCompat javaScriptSwitch = view.findViewById(R.id.javascript_switch); - SwitchCompat cookiesSwitch = view.findViewById(R.id.cookies_switch); - SwitchCompat domStorageSwitch = view.findViewById(R.id.dom_storage_switch); - SwitchCompat formDataSwitch = view.findViewById(R.id.form_data_switch); // Form data can be removed once the minimum API >= 26. - SwitchCompat easyListSwitch = view.findViewById(R.id.easylist_switch); - SwitchCompat easyPrivacySwitch = view.findViewById(R.id.easyprivacy_switch); - SwitchCompat fanboysAnnoyanceSwitch = view.findViewById(R.id.fanboys_annoyance_list_switch); - SwitchCompat fanboysSocialBlockingSwitch = view.findViewById(R.id.fanboys_social_blocking_list_switch); - SwitchCompat ultraListSwitch = view.findViewById(R.id.ultralist_switch); - SwitchCompat ultraPrivacySwitch = view.findViewById(R.id.ultraprivacy_switch); - SwitchCompat blockAllThirdPartyRequestsSwitch = view.findViewById(R.id.block_all_third_party_requests_switch); - Spinner userAgentSpinner = view.findViewById(R.id.user_agent_spinner); - EditText customUserAgentEditText = view.findViewById(R.id.custom_user_agent_edittext); - Spinner xRequestedWithHeaderSpinner = view.findViewById(R.id.x_requested_with_header_spinner); - Spinner fontSizeSpinner = view.findViewById(R.id.font_size_spinner); - EditText customFontSizeEditText = view.findViewById(R.id.custom_font_size_edittext); - Spinner swipeToRefreshSpinner = view.findViewById(R.id.swipe_to_refresh_spinner); - Spinner webViewThemeSpinner = view.findViewById(R.id.webview_theme_spinner); - Spinner wideViewportSpinner = view.findViewById(R.id.wide_viewport_spinner); - Spinner displayWebpageImagesSpinner = view.findViewById(R.id.display_webpage_images_spinner); - SwitchCompat pinnedSslCertificateSwitch = view.findViewById(R.id.pinned_ssl_certificate_switch); - RadioButton currentWebsiteCertificateRadioButton = view.findViewById(R.id.current_website_certificate_radiobutton); - SwitchCompat pinnedIpAddressesSwitch = view.findViewById(R.id.pinned_ip_addresses_switch); - RadioButton currentIpAddressesRadioButton = view.findViewById(R.id.current_ip_addresses_radiobutton); - - // Extract the data for the domain settings. - String domainNameString = domainNameEditText.getText().toString(); - boolean javaScript = javaScriptSwitch.isChecked(); - boolean cookies = cookiesSwitch.isChecked(); - boolean domStorage = domStorageSwitch.isChecked(); - boolean formData = formDataSwitch.isChecked(); // Form data can be removed once the minimum API >= 26. - boolean easyList = easyListSwitch.isChecked(); - boolean easyPrivacy = easyPrivacySwitch.isChecked(); - boolean fanboysAnnoyance = fanboysAnnoyanceSwitch.isChecked(); - boolean fanboysSocialBlocking = fanboysSocialBlockingSwitch.isChecked(); - boolean ultraList = ultraListSwitch.isChecked(); - boolean ultraPrivacy = ultraPrivacySwitch.isChecked(); - boolean blockAllThirdPartyRequests = blockAllThirdPartyRequestsSwitch.isChecked(); - int userAgentSwitchPosition = userAgentSpinner.getSelectedItemPosition(); - int xRequestedWithHeaderSwitchInt = xRequestedWithHeaderSpinner.getSelectedItemPosition(); - int fontSizeSwitchPosition = fontSizeSpinner.getSelectedItemPosition(); - int swipeToRefreshInt = swipeToRefreshSpinner.getSelectedItemPosition(); - int webViewThemeInt = webViewThemeSpinner.getSelectedItemPosition(); - int wideViewportInt = wideViewportSpinner.getSelectedItemPosition(); - int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition(); - boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked(); - boolean pinnedIpAddress = pinnedIpAddressesSwitch.isChecked(); - - // Initialize the user agent name string. - String userAgentName; - - // Set the user agent name. - switch (userAgentSwitchPosition) { - case MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT: - // Set the user agent name to be `System default user agent`. - userAgentName = resources.getString(R.string.system_default_user_agent); - break; - - case MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT: - // Set the user agent name to be the custom user agent. - userAgentName = customUserAgentEditText.getText().toString(); - break; - - default: - // Get the array of user agent names. - String[] userAgentNameArray = resources.getStringArray(R.array.user_agent_names); - - // 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. - userAgentName = userAgentNameArray[userAgentSwitchPosition - 1]; - } - - // Initialize the font size integer. `0` indicates the system default font size. - int fontSizeInt = 0; - - // Use a custom font size if it is selected. - if (fontSizeSwitchPosition == 1) { // A custom font size is specified. - // Get the custom font size from the edit text. - fontSizeInt = Integer.parseInt(customFontSizeEditText.getText().toString()); - } - - // Save the domain settings. - domainsDatabaseHelper.updateDomain(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScript, cookies, domStorage, formData, easyList, easyPrivacy, - fanboysAnnoyance, fanboysSocialBlocking, ultraList, ultraPrivacy, blockAllThirdPartyRequests, userAgentName, xRequestedWithHeaderSwitchInt, fontSizeInt, swipeToRefreshInt, webViewThemeInt, - wideViewportInt, displayWebpageImagesInt, pinnedSslCertificate, pinnedIpAddress); - - // Update the pinned SSL certificate if a new one is checked. - if (currentWebsiteCertificateRadioButton.isChecked()) { - // Update the database. - domainsDatabaseHelper.updatePinnedSslCertificate(currentDomainDatabaseId, sslIssuedToCName, sslIssuedToOName, sslIssuedToUName, sslIssuedByCName, sslIssuedByOName, sslIssuedByUName, - sslStartDateLong, sslEndDateLong); - } - - // Update the pinned IP addresses if new ones are checked. - if (currentIpAddressesRadioButton.isChecked()) { - // Update the database. - domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses); - } - } - - private void populateDomainsListView(final int highlightedDomainDatabaseId, int domainsListViewPosition) { - // get a handle for the current `domains_listview`. - domainsListView = findViewById(R.id.domains_listview); - - // Get a `Cursor` with the current contents of the domains database. - Cursor domainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain(); - - // Setup `domainsCursorAdapter` with `this` context. `false` disables `autoRequery`. - CursorAdapter domainsCursorAdapter = new CursorAdapter(getApplicationContext(), domainsCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. `false` does not attach it to the root. - return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Set the domain name. - String domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)); - TextView domainNameTextView = view.findViewById(R.id.domain_name_textview); - domainNameTextView.setText(domainNameString); - } - }; - - // Update the list view. - domainsListView.setAdapter(domainsCursorAdapter); - - // Restore the scroll position. - domainsListView.setSelection(domainsListViewPosition); - - // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain. - if (DomainsActivity.twoPanedMode && (domainsCursor.getCount() > 0)) { // Two-paned mode is enabled and there is at least one domain. - // Initialize `highlightedDomainPosition`. - int highlightedDomainPosition = 0; - - // Get the cursor position for the highlighted domain. - for (int i = 0; i < domainsCursor.getCount(); i++) { - // Move to position `i` in the cursor. - domainsCursor.moveToPosition(i); - - // Get the database ID for this position. - int currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID)); - - // Set `highlightedDomainPosition` if the database ID for this matches `highlightedDomainDatabaseId`. - if (highlightedDomainDatabaseId == currentDatabaseId) { - highlightedDomainPosition = i; - } - } - - // Select the highlighted domain. - domainsListView.setItemChecked(highlightedDomainPosition, true); - - // Get the database ID for the highlighted domain. - domainsCursor.moveToPosition(highlightedDomainPosition); - currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID)); - - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Store the domain settings in the arguments bundle. - argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId); - argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY); - - // Instantiate a new domain settings fragment. - DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment(); - - // Add the arguments bundle to the domain settings fragment. - domainSettingsFragment.setArguments(argumentsBundle); - - // Display the domain settings fragment. - getSupportFragmentManager().beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit(); - - // Enable the delete options menu items. - deleteMenuItem.setEnabled(true); - - // Set the delete icon. - deleteMenuItem.setIcon(R.drawable.delete_enabled); - } else if (twoPanedMode) { // Two-paned mode is enabled but there are no domains. - // Disable the options `MenuItems`. - deleteMenuItem.setEnabled(false); - deleteMenuItem.setIcon(R.drawable.delete_disabled); - } - } - - @Override - public void dismissSnackbar() { - // Dismiss the undo delete snackbar if it is shown. - if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) { - // Dismiss the snackbar. - undoDeleteSnackbar.dismiss(); - } - } - - @Override - public void onDestroy() { - // Close the domains database helper. - domainsDatabaseHelper.close(); - - // Run the default commands. - super.onDestroy(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt new file mode 100644 index 00000000..949b678a --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt @@ -0,0 +1,880 @@ +/* + * Copyright 2017-2022 Soren Stoutner . + * + * This file is part of Privacy Browser Android . + * + * Privacy Browser Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser Android. If not, see . + */ + +package com.stoutner.privacybrowser.activities + +import android.app.Activity +import android.content.Context +import android.database.Cursor +import android.os.Bundle +import android.os.Handler +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.EditText +import android.widget.ListView +import android.widget.RadioButton +import android.widget.ScrollView +import android.widget.Spinner +import android.widget.TextView +import androidx.activity.OnBackPressedCallback + +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SwitchCompat +import androidx.appcompat.widget.Toolbar +import androidx.core.app.NavUtils +import androidx.cursoradapter.widget.CursorAdapter +import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager + +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.snackbar.Snackbar + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.dialogs.AddDomainDialog.AddDomainListener +import com.stoutner.privacybrowser.dialogs.AddDomainDialog.Companion.addDomain +import com.stoutner.privacybrowser.fragments.DomainSettingsFragment +import com.stoutner.privacybrowser.fragments.DomainsListFragment +import com.stoutner.privacybrowser.fragments.DomainsListFragment.DismissSnackbarInterface +import com.stoutner.privacybrowser.fragments.DomainsListFragment.SaveDomainSettingsInterface +import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper + +// Define the class constants. +private const val DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_id" +private const val DOMAIN_SETTINGS_DISPLAYED = "domain_settings_displayed" +private const val DOMAIN_SETTINGS_SCROLL_Y = "domain_settings_scroll_y" +private const val LISTVIEW_POSITION = "listview_position" + +class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarInterface, SaveDomainSettingsInterface { + companion object { + // Define the public constants. + const val CLOSE_ON_BACK = "close_on_back" + const val CURRENT_IP_ADDRESSES = "current_ip_addresses" + const val CURRENT_URL = "current_url" + const val LOAD_DOMAIN = "load_domain" + const val SSL_END_DATE = "ssl_end_date" + const val SSL_ISSUED_BY_CNAME = "ssl_issued_by_cname" + const val SSL_ISSUED_BY_ONAME = "ssl_issued_by_oname" + const val SSL_ISSUED_BY_UNAME = "ssl_issued_by_uname" + const val SSL_ISSUED_TO_CNAME = "ssl_issued_to_cname" + const val SSL_ISSUED_TO_ONAME = "ssl_issued_to_oname" + const val SSL_ISSUED_TO_UNAME = "ssl_issued_to_uname" + const val SSL_START_DATE = "ssl_start_date" + + // Define the public variables. + var currentDomainDatabaseId = 0 // Used in `DomainsListFragment`. + var dismissingSnackbar = false // Used in `DomainsListFragment`. + var domainsListViewPosition = 0 // Used in `DomainsListFragment`. + var sslEndDateLong: Long = 0 // Used in `DomainsSettingsFragment`. + var sslStartDateLong: Long = 0 // Used in `DomainSettingsFragment`. + var twoPanedMode = false // Used in `DomainsListFragment`. + + // Declare the public views. They are used in `DomainsListFragment`. + lateinit var deleteMenuItem: MenuItem + + // Declare the SSL certificate and IP address strings. + var currentIpAddresses: String? = null // Used in `DomainSettingsFragment`. + var sslIssuedToCName: String? = null // Used in `DomainSettingsFragment`. + var sslIssuedToOName: String? = null // Used in `DomainSettingsFragment`. + var sslIssuedToUName: String? = null // Used in `DomainSettingsFragment`. + var sslIssuedByCName: String? = null // Used in `DomainSettingsFragment`. + var sslIssuedByOName: String? = null // Used in `DomainSettingsFragment`. + var sslIssuedByUName: String? = null // Used in `DomainSettingsFragment`. + } + + // Declare the class views. + private lateinit var addDomainFAB: FloatingActionButton + private lateinit var coordinatorLayout: View + private var domainsListView: ListView? = null + private var undoDeleteSnackbar: Snackbar? = null + + // Declare the class variables. + private lateinit var domainsDatabaseHelper: DomainsDatabaseHelper + + // Define the class variables. + private var appRestarted = false + private var closeActivityAfterDismissingSnackbar = false + private var closeOnBack = false + private var deletedDomainPosition = 0 + private var domainSettingsDatabaseIdBeforeRestart = 0 + private var domainSettingsDisplayedBeforeRestart = false + private var domainSettingsScrollY = 0 + private var goDirectlyToDatabaseId = -1 + + override fun onCreate(savedInstanceState: Bundle?) { + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + + // Get the preferences. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false) + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // Run the default commands. + super.onCreate(savedInstanceState) + + // Process the saved instance state if it is not null. + if (savedInstanceState != null) { + // Extract the values from the saved instance state if it is not null. + domainsListViewPosition = savedInstanceState.getInt(LISTVIEW_POSITION) + domainSettingsDisplayedBeforeRestart = savedInstanceState.getBoolean(DOMAIN_SETTINGS_DISPLAYED) + domainSettingsDatabaseIdBeforeRestart = savedInstanceState.getInt(DOMAIN_SETTINGS_DATABASE_ID) + domainSettingsScrollY = savedInstanceState.getInt(DOMAIN_SETTINGS_SCROLL_Y) + + // Set the app restarted flag. + appRestarted = true + } + + // Get the launching intent + val intent = intent + + // Extract the domain to load if there is one. `-1` is the default value. + goDirectlyToDatabaseId = intent.getIntExtra(LOAD_DOMAIN, -1) + + // Get the status of close-on-back, which is true when the domains activity is called from the options menu. + closeOnBack = intent.getBooleanExtra(CLOSE_ON_BACK, false) + + // Get the current URL. + val currentUrl = intent.getStringExtra(CURRENT_URL) + + // Store the current SSL certificate information in class variables. + sslIssuedToCName = intent.getStringExtra(SSL_ISSUED_TO_CNAME) + sslIssuedToOName = intent.getStringExtra(SSL_ISSUED_TO_ONAME) + sslIssuedToUName = intent.getStringExtra(SSL_ISSUED_TO_UNAME) + sslIssuedByCName = intent.getStringExtra(SSL_ISSUED_BY_CNAME) + sslIssuedByOName = intent.getStringExtra(SSL_ISSUED_BY_ONAME) + sslIssuedByUName = intent.getStringExtra(SSL_ISSUED_BY_UNAME) + sslStartDateLong = intent.getLongExtra(SSL_START_DATE, 0) + sslEndDateLong = intent.getLongExtra(SSL_END_DATE, 0) + currentIpAddresses = intent.getStringExtra(CURRENT_IP_ADDRESSES) + + // Set the content view. + if (bottomAppBar) + setContentView(R.layout.domains_bottom_appbar) + else + setContentView(R.layout.domains_top_appbar) + + // Get handles for the views. + coordinatorLayout = findViewById(R.id.domains_coordinatorlayout) + val toolbar = findViewById(R.id.domains_toolbar) + addDomainFAB = findViewById(R.id.add_domain_fab) + + // Set the support action bar. + setSupportActionBar(toolbar) + + // Get a handle for the action bar. + val actionBar = supportActionBar!! + + // Set the back arrow on the action bar. + actionBar.setDisplayHomeAsUpEnabled(true) + + // Initialize the database handler. + domainsDatabaseHelper = DomainsDatabaseHelper(this) + + // Determine if the device is in two pane mode. `domain_settings_fragment_container` does not exist on devices with a width less than 900dp. + twoPanedMode = (findViewById(R.id.domain_settings_fragment_container) != null) + + // Configure the add domain floating action button. + addDomainFAB.setOnClickListener { + // Create an add domain dialog. + val addDomainDialog: DialogFragment = addDomain(currentUrl) + + // Show the add domain dialog. + addDomainDialog.show(supportFragmentManager, resources.getString(R.string.add_domain)) + } + + // Get a handle for the activity. + val activity: Activity = this + + // Control what the navigation bar back button does. + val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (twoPanedMode) { // The device is in two-paned mode. + // Save the current domain settings if the domain settings fragment is displayed. + if (findViewById(R.id.domain_settings_scrollview) != null) + saveDomainSettings(coordinatorLayout) + + // Dismiss the undo delete snackbar if it is shown. + if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { + // Set the close flag. + closeActivityAfterDismissingSnackbar = true + + // Dismiss the snackbar. + undoDeleteSnackbar!!.dismiss() + } else { + // Go home. + NavUtils.navigateUpFromSameTask(activity) + } + } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu. + // Save the current domain settings. + saveDomainSettings(coordinatorLayout) + + // Go home. + NavUtils.navigateUpFromSameTask(activity) + } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and domain settings fragment is displayed. + // Save the current domain settings. + saveDomainSettings(coordinatorLayout) + + // Instantiate a new domains list fragment. + val domainsListFragment = DomainsListFragment() + + // Display the domains list fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + + // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode. + populateDomainsListView(-1, domainsListViewPosition) + + // Show the add domain floating action button. + addDomainFAB.show() + + // Hide the delete menu item. + deleteMenuItem.isVisible = false + } else { // The device is in single-paned mode and the domain list fragment is displayed. + // Dismiss the undo delete SnackBar if it is shown. + if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { + // Set the close flag. + closeActivityAfterDismissingSnackbar = true + + // Dismiss the snackbar. + undoDeleteSnackbar!!.dismiss() + } else { + // Go home. + NavUtils.navigateUpFromSameTask(activity) + } + } + } + } + + // Register the on back pressed callback. + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu. + menuInflater.inflate(R.menu.domains_options_menu, menu) + + // Get a handle for the delete menu item. + deleteMenuItem = menu.findItem(R.id.delete_domain) + + // Only display the delete menu item (initially) in two-paned mode. + deleteMenuItem.isVisible = twoPanedMode + + // Display the fragments. This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated. + if (appRestarted && domainSettingsDisplayedBeforeRestart) { // The app was restarted, possibly because the device was rotated, and domain settings were displayed previously. + // Reset the app restarted flag. + appRestarted = false + + if (twoPanedMode) { // The device is in two-paned mode. + // Initialize the domains list fragment. + val domainsListFragment = DomainsListFragment() + + // Display the domains list fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + + // Populate the list of domains and highlight the domain that was highlighted before the restart. + populateDomainsListView(domainSettingsDatabaseIdBeforeRestart, domainsListViewPosition) + } else { // The device is in single-paned mode. + // Store the current domain database ID. + currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRestart + + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Add the domain settings arguments. + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId) + argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY) + + // Instantiate a new domain settings fragment. + val domainSettingsFragment = DomainSettingsFragment() + + // Add the arguments bundle to the domain settings fragment. + domainSettingsFragment.arguments = argumentsBundle + + // Show the delete menu item. + deleteMenuItem.isVisible = true + + // Hide the add domain floating action button. + addDomainFAB.hide() + + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() + } + } else { // The device was not restarted or, if it was, domain settings were not displayed previously. + if (goDirectlyToDatabaseId >= 0) { // Load the indicated domain settings. + // Store the current domain database ID. + currentDomainDatabaseId = goDirectlyToDatabaseId + + // Check if the device is in two-paned mode. + if (twoPanedMode) { // The device is in two-paned mode. + // Instantiate a new domains list fragment. + val domainsListFragment = DomainsListFragment() + + // Display the domains list fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + + // Populate the list of domains. + populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition) + } else { // The device is in single-paned mode. + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Add the domain settings to arguments bundle. + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId) + argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY) + + // Instantiate a new domain settings fragment. + val domainSettingsFragment = DomainSettingsFragment() + + // Add the arguments bundle to the domain settings fragment. + domainSettingsFragment.arguments = argumentsBundle + + // Show the delete menu item. + deleteMenuItem.isVisible = true + + // Hide the add domain floating action button. + addDomainFAB.hide() + + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() + } + } else { // Highlight the first domain. + // Instantiate a new domains list fragment. + val domainsListFragment = DomainsListFragment() + + // Display the domain list fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + + // Populate the list of domains. `-1` highlights the first domain. + populateDomainsListView(-1, domainsListViewPosition) + } + } + + // Success! + return true + } + + override fun onOptionsItemSelected(menuItem: MenuItem): Boolean { + // Run the command according to the selected menu item. + when (menuItem.itemId) { + android.R.id.home -> { // The home arrow is identified as `android.R.id.home`, not just `R.id.home`. + // Check if the device is in two-paned mode. + if (twoPanedMode) { // The device is in two-paned mode. + // Save the current domain settings if the domain settings fragment is displayed. + if (findViewById(R.id.domain_settings_scrollview) != null) + saveDomainSettings(coordinatorLayout) + + // Dismiss the undo delete snackbar if it is shown. + if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { + // Set the close flag. + closeActivityAfterDismissingSnackbar = true + + // Dismiss the snackbar. + undoDeleteSnackbar!!.dismiss() + } else { + // Go home. + NavUtils.navigateUpFromSameTask(this) + } + } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu. + // Save the current domain settings. + saveDomainSettings(coordinatorLayout) + + // Go home. + NavUtils.navigateUpFromSameTask(this) + } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed. + // Save the current domain settings. + saveDomainSettings(coordinatorLayout) + + // Instantiate a new domains list fragment. + val domainsListFragment = DomainsListFragment() + + // Display the domains list fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + + // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode. + populateDomainsListView(-1, domainsListViewPosition) + + // Show the add domain floating action button. + addDomainFAB.show() + + // Hide the delete menu item. + deleteMenuItem.isVisible = false + } else { // The device is in single-paned mode and domains list fragment is displayed. + // Dismiss the undo delete snackbar if it is shown. + if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { + // Set the close flag. + closeActivityAfterDismissingSnackbar = true + + // Dismiss the snackbar. + undoDeleteSnackbar!!.dismiss() + } else { + // Go home. + NavUtils.navigateUpFromSameTask(this) + } + } + } + + R.id.delete_domain -> { // Delete. + // Get a handle for the activity (used in an inner class below). + val activity: Activity = this + + // Check to see if the domain settings were loaded directly for editing of this app in single-paned mode. + if (closeOnBack && !twoPanedMode) { // The activity should delete the domain settings and exit straight to the the main WebView activity. + // Delete the selected domain. + domainsDatabaseHelper.deleteDomain(currentDomainDatabaseId) + + // Go home. + NavUtils.navigateUpFromSameTask(activity) + } else { // A snackbar should be shown before deleting the domain settings. + // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists. + closeOnBack = false + + // Store a copy of the current domain database ID because it could change while the snackbar is displayed. + val databaseIdToDelete = currentDomainDatabaseId + + // Update the fragments and menu items. + if (twoPanedMode) { // Two-paned mode. + // Store the deleted domain position, which is needed if undo is selected in the snackbar. + deletedDomainPosition = domainsListView!!.checkedItemPosition + + // Disable the delete menu item. + deleteMenuItem.isEnabled = false + + // Remove the domain settings fragment. + supportFragmentManager.beginTransaction().remove(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!!).commitNow() + } else { // Single-paned mode. + // Instantiate a new domains list fragment. + val domainsListFragment = DomainsListFragment() + + // Display the domains list fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + + // Show the add domain floating action button. + addDomainFAB.show() + + // Hide the delete menu item. + deleteMenuItem.isVisible = false + } + + // Get a cursor that does not show the domain to be deleted. + val domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete) + + // Setup the domains pending delete cursor adapter. + val domainsPendingDeleteCursorAdapter: CursorAdapter = object : CursorAdapter(this, domainsPendingDeleteCursor, false) { + override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { + // Inflate the individual item layout. + return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false) + } + + override fun bindView(view: View, context: Context, cursor: Cursor) { + // Get the domain name string. + val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)) + + // Get a handle for the domain name text view. + val domainNameTextView = view.findViewById(R.id.domain_name_textview) + + // Display the domain name. + domainNameTextView.text = domainNameString + } + } + + // Update the handle for the current domains list view. + domainsListView = findViewById(R.id.domains_listview) + + // Update the list view. + domainsListView!!.adapter = domainsPendingDeleteCursorAdapter + + // Display a snackbar. + undoDeleteSnackbar = Snackbar.make(domainsListView!!, R.string.domain_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo) {} // Do nothing because everything will be handled by `onDismissed()` below. + .addCallback(object : Snackbar.Callback() { + override fun onDismissed(snackbar: Snackbar, event: Int) { + // Run commands based on the event. + if (event == DISMISS_EVENT_ACTION) { // The user pushed the `Undo` button. + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the domains settings in the arguments bundle. + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete) + argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY) + + // Instantiate a new domain settings fragment. + val domainSettingsFragment = DomainSettingsFragment() + + // Add the arguments bundle to the domain settings fragment. + domainSettingsFragment.arguments = argumentsBundle + + // Display the correct fragments. + if (twoPanedMode) { // The device in in two-paned mode. + // Get a cursor with the current contents of the domains database. + val undoDeleteDomainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain + + // Setup the domains cursor adapter. + val undoDeleteDomainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, undoDeleteDomainsCursor, false) { + override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { + // Inflate the individual item layout. + return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false) + } + + override fun bindView(view: View, context: Context, cursor: Cursor) { + /// Get the domain name string. + val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)) + + // Get a handle for the domain name text view. + val domainNameTextView = view.findViewById(R.id.domain_name_textview) + + // Display the domain name. + domainNameTextView.text = domainNameString + } + } + + // Update the domains list view. + domainsListView!!.adapter = undoDeleteDomainsCursorAdapter + + // Select the previously deleted domain in the list view. + domainsListView!!.setItemChecked(deletedDomainPosition, true) + + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commitNow() + + // Enable the delete menu item. + deleteMenuItem.isEnabled = true + } else { // The device in in one-paned mode. + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() + + // Hide the add domain floating action button. + addDomainFAB.hide() + + // Show and enable the delete menu item. + deleteMenuItem.isVisible = true + + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() + } + } else { // The snackbar was dismissed without the undo button being pushed. + // Delete the selected domain. + domainsDatabaseHelper.deleteDomain(databaseIdToDelete) + + // Enable the delete menu item if the system was waiting for a snackbar to be dismissed. + if (dismissingSnackbar) { + // Create a runnable to enable the delete menu item. + val enableDeleteMenuItemRunnable = Runnable { + // Enable or show the delete menu item according to the display mode. + if (twoPanedMode) + deleteMenuItem.isEnabled = true + else + deleteMenuItem.isVisible = true + + // Reset the dismissing snackbar tracker. + dismissingSnackbar = false + } + + // Instantiate a handler running the main looper. + val handler = Handler(mainLooper) + + // Enable or show the delete menu icon after 100 milliseconds to make sure that the previous domain has been deleted from the database. + handler.postDelayed(enableDeleteMenuItemRunnable, 100) + } + + // Close the activity if back was pressed. + if (closeActivityAfterDismissingSnackbar) + NavUtils.navigateUpFromSameTask(activity) + } + } + }) + + // Show the Snackbar. + undoDeleteSnackbar!!.show() + } + } + } + + // Consume the event. + return true + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + // Run the default commands. + super.onSaveInstanceState(savedInstanceState) + + // Get a handle for the domain settings scrollview. + val domainSettingsScrollView = findViewById(R.id.domain_settings_scrollview) + + // Check to see if the domain settings scrollview exists. + if (domainSettingsScrollView == null) { // The domain settings are not displayed. + // Store the domain settings status in the bundle. + savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false) + savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1) + savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0) + } else { // The domain settings are displayed. + // Save any changes that have been made to the domain settings. + saveDomainSettings(coordinatorLayout) + + // Get the domain settings scroll Y. + val domainSettingsScrollY = domainSettingsScrollView.scrollY + + // Store the domain settings status in the bundle. + savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true) + savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId) + savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY) + } + + // Check to see if the domains listview exists. + if (domainsListView != null) { + // Get the domains listview position. + val domainsListViewPosition = domainsListView!!.firstVisiblePosition + + // Store the listview position in the bundle. + savedInstanceState.putInt(LISTVIEW_POSITION, domainsListViewPosition) + } + } + + override fun onAddDomain(dialogFragment: DialogFragment) { + // Dismiss the undo delete snackbar if it is currently displayed. + if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) + undoDeleteSnackbar!!.dismiss() + + // Get a handle for the domain name edit text. + val domainNameEditText = dialogFragment.dialog!!.findViewById(R.id.domain_name_edittext) + + // Get the domain name string. + val domainNameString = domainNameEditText.text.toString() + + // Create the domain and store the database ID in the current domain database ID. + currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString) + + // Display the newly created domain. + if (twoPanedMode) { // The device in in two-paned mode. + populateDomainsListView(currentDomainDatabaseId, 0) + } else { // The device is in single-paned mode. + // Hide the add domain floating action button. + addDomainFAB.hide() + + // Show and enable the delete menu item. + deleteMenuItem.isVisible = true + + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Add the domain settings to the arguments bundle. The scroll Y should always be `0` on a new domain. + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId) + argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, 0) + + // Instantiate a new domain settings fragment. + val domainSettingsFragment = DomainSettingsFragment() + + // Add the arguments bundle to the domain setting fragment. + domainSettingsFragment.arguments = argumentsBundle + + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() + } + } + + override fun saveDomainSettings(view: View) { + // Get handles for the domain settings. + val domainNameEditText = view.findViewById(R.id.domain_settings_name_edittext) + val javaScriptSwitch = view.findViewById(R.id.javascript_switch) + val cookiesSwitch = view.findViewById(R.id.cookies_switch) + val domStorageSwitch = view.findViewById(R.id.dom_storage_switch) + val formDataSwitch = view.findViewById(R.id.form_data_switch) // Form data can be removed once the minimum API >= 26. + val easyListSwitch = view.findViewById(R.id.easylist_switch) + val easyPrivacySwitch = view.findViewById(R.id.easyprivacy_switch) + val fanboysAnnoyanceSwitch = view.findViewById(R.id.fanboys_annoyance_list_switch) + val fanboysSocialBlockingSwitch = view.findViewById(R.id.fanboys_social_blocking_list_switch) + val ultraListSwitch = view.findViewById(R.id.ultralist_switch) + val ultraPrivacySwitch = view.findViewById(R.id.ultraprivacy_switch) + val blockAllThirdPartyRequestsSwitch = view.findViewById(R.id.block_all_third_party_requests_switch) + val userAgentSpinner = view.findViewById(R.id.user_agent_spinner) + val customUserAgentEditText = view.findViewById(R.id.custom_user_agent_edittext) + val xRequestedWithHeaderSpinner = view.findViewById(R.id.x_requested_with_header_spinner) + val fontSizeSpinner = view.findViewById(R.id.font_size_spinner) + val customFontSizeEditText = view.findViewById(R.id.custom_font_size_edittext) + val swipeToRefreshSpinner = view.findViewById(R.id.swipe_to_refresh_spinner) + val webViewThemeSpinner = view.findViewById(R.id.webview_theme_spinner) + val wideViewportSpinner = view.findViewById(R.id.wide_viewport_spinner) + val displayWebpageImagesSpinner = view.findViewById(R.id.display_webpage_images_spinner) + val pinnedSslCertificateSwitch = view.findViewById(R.id.pinned_ssl_certificate_switch) + val currentWebsiteCertificateRadioButton = view.findViewById(R.id.current_website_certificate_radiobutton) + val pinnedIpAddressesSwitch = view.findViewById(R.id.pinned_ip_addresses_switch) + val currentIpAddressesRadioButton = view.findViewById(R.id.current_ip_addresses_radiobutton) + + // Extract the data for the domain settings. + val domainNameString = domainNameEditText.text.toString() + val javaScript = javaScriptSwitch.isChecked + val cookies = cookiesSwitch.isChecked + val domStorage = domStorageSwitch.isChecked + val formData = formDataSwitch.isChecked // Form data can be removed once the minimum API >= 26. + val easyList = easyListSwitch.isChecked + val easyPrivacy = easyPrivacySwitch.isChecked + val fanboysAnnoyance = fanboysAnnoyanceSwitch.isChecked + val fanboysSocialBlocking = fanboysSocialBlockingSwitch.isChecked + val ultraList = ultraListSwitch.isChecked + val ultraPrivacy = ultraPrivacySwitch.isChecked + val blockAllThirdPartyRequests = blockAllThirdPartyRequestsSwitch.isChecked + val userAgentSwitchPosition = userAgentSpinner.selectedItemPosition + val xRequestedWithHeaderSwitchInt = xRequestedWithHeaderSpinner.selectedItemPosition + val fontSizeSwitchPosition = fontSizeSpinner.selectedItemPosition + val swipeToRefreshInt = swipeToRefreshSpinner.selectedItemPosition + val webViewThemeInt = webViewThemeSpinner.selectedItemPosition + val wideViewportInt = wideViewportSpinner.selectedItemPosition + val displayWebpageImagesInt = displayWebpageImagesSpinner.selectedItemPosition + val pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked + val pinnedIpAddress = pinnedIpAddressesSwitch.isChecked + + // Get the user agent name. + val userAgentName: String = when (userAgentSwitchPosition) { + 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`. + MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT -> customUserAgentEditText.text.toString() // Set the user agent name to be the custom user agent. + else -> { + // Get the array of user agent names. + val userAgentNameArray = resources.getStringArray(R.array.user_agent_names) + + // 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. + userAgentNameArray[userAgentSwitchPosition - 1] + } + } + + // Initialize the font size integer. `0` indicates the system default font size. + var fontSizeInt = 0 + + // Use a custom font size if it is selected. + if (fontSizeSwitchPosition == 1) + fontSizeInt = customFontSizeEditText.text.toString().toInt() + + // Save the domain settings. + domainsDatabaseHelper.updateDomain(currentDomainDatabaseId, domainNameString, javaScript, cookies, domStorage, formData, easyList, easyPrivacy, fanboysAnnoyance, fanboysSocialBlocking, ultraList, + ultraPrivacy, blockAllThirdPartyRequests, userAgentName, xRequestedWithHeaderSwitchInt, fontSizeInt, swipeToRefreshInt, webViewThemeInt, wideViewportInt, displayWebpageImagesInt, + pinnedSslCertificate, pinnedIpAddress) + + // Update the pinned SSL certificate if a new one is checked. + if (currentWebsiteCertificateRadioButton.isChecked) + domainsDatabaseHelper.updatePinnedSslCertificate(currentDomainDatabaseId, sslIssuedToCName!!, sslIssuedToOName!!, sslIssuedToUName!!, sslIssuedByCName!!, sslIssuedByOName!!, sslIssuedByUName!!, + sslStartDateLong, sslEndDateLong) + + // Update the pinned IP addresses if new ones are checked. + if (currentIpAddressesRadioButton.isChecked) + domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses!!) + } + + private fun populateDomainsListView(highlightedDomainDatabaseId: Int, domainsListViewPosition: Int) { + // Get a cursor with the current contents of the domains database. + val domainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain + + // Setup the domains cursor adapter. + val domainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, domainsCursor, false) { + override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { + // Inflate the individual item layout. + return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false) + } + + override fun bindView(view: View, context: Context, cursor: Cursor) { + // Get a handle for the domain name text view. + val domainNameTextView = view.findViewById(R.id.domain_name_textview) + + // Get the domain name string. + val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)) + + // Set the domain name. + domainNameTextView.text = domainNameString + } + } + + // get a handle for the current domains listview. + domainsListView = findViewById(R.id.domains_listview) + + // Update the list view. + domainsListView!!.adapter = domainsCursorAdapter + + // Restore the scroll position. + domainsListView!!.setSelection(domainsListViewPosition) + + // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain. + if (twoPanedMode && domainsCursor.count > 0) { // Two-paned mode is enabled and there is at least one domain. + // Initialize the highlighted domain position tracker. + var highlightedDomainPosition = 0 + + // Get the cursor position for the highlighted domain. + for (i in 0 until domainsCursor.count) { + // Move to position `i` in the cursor. + domainsCursor.moveToPosition(i) + + // Get the database ID for this position. + val currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID)) + + // Set the highlighted domain position if the database ID for this matches the highlighted domain database ID. + if (highlightedDomainDatabaseId == currentDatabaseId) + highlightedDomainPosition = i + } + + // Select the highlighted domain. + domainsListView!!.setItemChecked(highlightedDomainPosition, true) + + // Move to the highlighted domain. + domainsCursor.moveToPosition(highlightedDomainPosition) + + // Get the database ID for the highlighted domain. + currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID)) + + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the domain settings in the arguments bundle. + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId) + argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY) + + // Instantiate a new domain settings fragment. + val domainSettingsFragment = DomainSettingsFragment() + + // Add the arguments bundle to the domain settings fragment. + domainSettingsFragment.arguments = argumentsBundle + + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit() + + // Enable the delete options menu items. + deleteMenuItem.isEnabled = true + } else if (twoPanedMode) { // Two-paned mode is enabled but there are no domains. + // Disable the delete menu item. + deleteMenuItem.isEnabled = false + } + } + + override fun dismissSnackbar() { + // Dismiss the undo delete snackbar if it is shown. + if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { + // Dismiss the snackbar. + undoDeleteSnackbar!!.dismiss() + } + } + + public override fun onDestroy() { + // Close the domains database helper. + domainsDatabaseHelper.close() + + // Run the default commands. + super.onDestroy() + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.java deleted file mode 100644 index 7a165d73..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright © 2016-2022 Soren Stoutner . - * - * This file is part of Privacy Browser Android . - * - * Privacy Browser Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privacy Browser Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privacy Browser Android. If not, see . - */ - -package com.stoutner.privacybrowser.activities; - -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.view.WindowManager; - -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; -import androidx.viewpager.widget.ViewPager; - -import com.google.android.material.tabs.TabLayout; - -import com.stoutner.privacybrowser.adapters.GuidePagerAdapter; -import com.stoutner.privacybrowser.R; - -public class GuideActivity extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Get the preferences. - boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false); - boolean bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - // Run the default commands. - super.onCreate(savedInstanceState); - - // Set the content view. - if (bottomAppBar) { - setContentView(R.layout.guide_bottom_appbar); - } else { - setContentView(R.layout.guide_top_appbar); - } - - // Get a handle for the toolbar. - Toolbar toolbar = findViewById(R.id.guide_toolbar); - - // Set the support action bar. - setSupportActionBar(toolbar); - - // Get a handle for the action bar. - final ActionBar actionBar = getSupportActionBar(); - - // Remove the incorrect lint warning that the action bar might be null. - assert actionBar != null; - - // Display the home arrow on the action bar. - actionBar.setDisplayHomeAsUpEnabled(true); - - // Get a handle for the view pager and the tab layout. - ViewPager aboutViewPager = findViewById(R.id.guide_viewpager); - TabLayout aboutTabLayout = findViewById(R.id.guide_tablayout); - - // Remove the incorrect lint warnings that the views might be null - assert aboutViewPager != null; - assert aboutTabLayout != null; - - // Set the view pager adapter. - aboutViewPager.setAdapter(new GuidePagerAdapter(getSupportFragmentManager(), getApplicationContext())); - - // Keep all the tabs in memory. - aboutViewPager.setOffscreenPageLimit(10); - - // Link the tab layout to the view pager. - aboutTabLayout.setupWithViewPager(aboutViewPager); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.kt new file mode 100644 index 00000000..e73608a5 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.kt @@ -0,0 +1,85 @@ +/* + * Copyright © 2016-2022 Soren Stoutner . + * + * This file is part of Privacy Browser Android . + * + * Privacy Browser Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser Android. If not, see . + */ + +package com.stoutner.privacybrowser.activities + +import android.os.Bundle +import android.view.WindowManager + +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.preference.PreferenceManager +import androidx.viewpager.widget.ViewPager + +import com.google.android.material.tabs.TabLayout + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.adapters.GuidePagerAdapter + +class GuideActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + + // Get the preferences. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false) + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // Run the default commands. + super.onCreate(savedInstanceState) + + // Set the content view. + if (bottomAppBar) { + setContentView(R.layout.guide_bottom_appbar) + } else { + setContentView(R.layout.guide_top_appbar) + } + + // Get handles for the views. + val toolbar = findViewById(R.id.guide_toolbar) + val guideViewPager = findViewById(R.id.guide_viewpager) + val guideTabLayout = findViewById(R.id.guide_tablayout) + + // Set the support action bar. + setSupportActionBar(toolbar) + + // Get a handle for the action bar. + val actionBar = supportActionBar!! + + // Display the home arrow on the action bar. + actionBar.setDisplayHomeAsUpEnabled(true) + + // Initialize the guide pager adapter. + val guidePagerAdapter = GuidePagerAdapter(supportFragmentManager, applicationContext) + + // Set the view pager adapter. + guideViewPager.adapter = guidePagerAdapter + + // Keep all the tabs in memory. This prevents the memory usage adapter from running multiple times. + guideViewPager.offscreenPageLimit = 10 + + // Link the tab layout to the view pager. + guideTabLayout.setupWithViewPager(guideViewPager) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 8e01f287..2b9ad637 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -1692,7 +1692,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.user_agent_custom) { // User Agent - Custom. // Update the user agent. - currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); + currentWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))); // Reload the current WebView. currentWebView.reload(); @@ -1915,19 +1915,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Consume the event. return true; } else if (menuItemId == R.id.add_or_edit_domain) { // Add or edit domain. + // Reapply the domain settings on returning to `MainWebViewActivity`. + reapplyDomainSettingsOnRestart = true; + // Check if domain settings currently exist. if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings. - // Reapply the domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - // Create an intent to launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); // Add the extra information to the intent. - domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId()); - domainsIntent.putExtra("close_on_back", true); - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); + domainsIntent.putExtra(DomainsActivity.LOAD_DOMAIN, currentWebView.getDomainSettingsDatabaseId()); + domainsIntent.putExtra(DomainsActivity.CLOSE_ON_BACK, true); + domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl()); + domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -1945,26 +1945,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook long endDateLong = sslCertificate.getValidNotAfterDate().getTime(); // Add the certificate to the intent. - domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName); - domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName); - domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName); - domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName); - domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName); - domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName); - domainsIntent.putExtra("ssl_start_date", startDateLong); - domainsIntent.putExtra("ssl_end_date", endDateLong); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_CNAME, issuedToCName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_ONAME, issuedToOName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_UNAME, issuedToUName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_CNAME, issuedByCName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_ONAME, issuedByOName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_UNAME, issuedByUName); + domainsIntent.putExtra(DomainsActivity.SSL_START_DATE, startDateLong); + domainsIntent.putExtra(DomainsActivity.SSL_END_DATE, endDateLong); } // Make it so. startActivity(domainsIntent); } else { // Add a new domain. - // Apply the new domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - - // Get the current domain + // Get the current URI. Uri currentUri = Uri.parse(currentWebView.getUrl()); + + // Get the current domain from the URI. String currentDomain = currentUri.getHost(); + // Set an empty domain if it is null. + if (currentDomain == null) + currentDomain = ""; + // Create the domain and store the database ID. int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain); @@ -1972,10 +1975,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Intent domainsIntent = new Intent(this, DomainsActivity.class); // Add the extra information to the intent. - domainsIntent.putExtra("load_domain", newDomainDatabaseId); - domainsIntent.putExtra("close_on_back", true); - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); + domainsIntent.putExtra(DomainsActivity.LOAD_DOMAIN, newDomainDatabaseId); + domainsIntent.putExtra(DomainsActivity.CLOSE_ON_BACK, true); + domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl()); + domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -1993,14 +1996,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook long endDateLong = sslCertificate.getValidNotAfterDate().getTime(); // Add the certificate to the intent. - domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName); - domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName); - domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName); - domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName); - domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName); - domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName); - domainsIntent.putExtra("ssl_start_date", startDateLong); - domainsIntent.putExtra("ssl_end_date", endDateLong); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_CNAME, issuedToCName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_ONAME, issuedToOName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_UNAME, issuedToUName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_CNAME, issuedByCName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_ONAME, issuedByOName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_UNAME, issuedByUName); + domainsIntent.putExtra(DomainsActivity.SSL_START_DATE, startDateLong); + domainsIntent.putExtra(DomainsActivity.SSL_END_DATE, endDateLong); } // Make it so. @@ -2138,8 +2141,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Intent domainsIntent = new Intent(this, DomainsActivity.class); // Add the extra information to the intent. - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); + domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl()); + domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -3794,12 +3797,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store the general preference information. boolean defaultXRequestedWithHeader = sharedPreferences.getBoolean(getString(R.string.x_requested_with_header_key), true); - String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value)); - String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value)); - boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); - String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value)); - boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true); - boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true); + String defaultFontSizeString = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value)); + String defaultUserAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value)); + boolean defaultSwipeToRefresh = sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true); + String webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value)); + boolean wideViewport = sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true); + boolean displayWebpageImages = sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true); // Get the WebView theme entry values string array. String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values); @@ -3929,7 +3932,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case SETTINGS_CUSTOM_USER_AGENT: // Set the default custom user agent. - nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); + nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))); break; default: @@ -4165,7 +4168,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case SETTINGS_CUSTOM_USER_AGENT: // Set the default custom user agent. - nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); + nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))); break; default: @@ -5097,7 +5100,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get the WebView theme. - String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value)); + String webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value)); // Get the WebView theme entry values string array. String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values); diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java b/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java deleted file mode 100644 index eca03832..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright © 2016-2020,2022 Soren Stoutner . - * - * This file is part of Privacy Browser Android . - * - * Privacy Browser Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privacy Browser Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privacy Browser Android. If not, see . - */ - -package com.stoutner.privacybrowser.adapters; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.fragments.GuideWebViewFragment; - -public class GuidePagerAdapter extends FragmentPagerAdapter { - // Define the class variables. - private final Context context; - - // The default constructor. - public GuidePagerAdapter(FragmentManager fragmentManager, Context context) { - // Run the default commands. - super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - - // Store the class variables. - this.context = context; - } - - @Override - // Get the count of the number of tabs. - public int getCount() { - return 9; - } - - @Override - // Get the name of each tab. Tab numbers start at 0. - public CharSequence getPageTitle(int tab) { - switch (tab) { - case 0: - return context.getString(R.string.overview); - - case 1: - return context.getString(R.string.javascript); - - case 2: - return context.getString(R.string.local_storage); - - case 3: - return context.getString(R.string.user_agent); - - case 4: - return context.getString(R.string.requests); - - case 5: - return context.getString(R.string.domain_settings); - - case 6: - return context.getString(R.string.ssl_certificates); - - case 7: - return context.getString(R.string.proxies); - - case 8: - return context.getString(R.string.tracking_ids); - - default: - return ""; - } - } - - @Override - @NonNull - // Setup each tab. - public Fragment getItem(int tabNumber) { - return GuideWebViewFragment.createTab(tabNumber); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.kt b/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.kt new file mode 100644 index 00000000..9f058f1e --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.kt @@ -0,0 +1,57 @@ +/* + * Copyright © 2016-2020,2022 Soren Stoutner . + * + * This file is part of Privacy Browser Android . + * + * Privacy Browser Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser Android. If not, see . + */ + +package com.stoutner.privacybrowser.adapters + +import android.content.Context + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.fragments.GuideWebViewFragment + +class GuidePagerAdapter(fragmentManager: FragmentManager, private val context: Context) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + // Get the count of the number of tabs. + override fun getCount(): Int { + return 9 + } + + // Get the name of each tab. Tab numbers start at 0. + override fun getPageTitle(tab: Int): CharSequence { + return when (tab) { + 0 -> context.getString(R.string.overview) + 1 -> context.getString(R.string.javascript) + 2 -> context.getString(R.string.local_storage) + 3 -> context.getString(R.string.user_agent) + 4 -> context.getString(R.string.requests) + 5 -> context.getString(R.string.domain_settings) + 6 -> context.getString(R.string.ssl_certificates) + 7 -> context.getString(R.string.proxies) + 8 -> context.getString(R.string.tracking_ids) + else -> "" + } + } + + // Setup each tab. + override fun getItem(tabNumber: Int): Fragment { + return GuideWebViewFragment.createTab(tabNumber) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt index 1eb8858a..0b2d6ed0 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt @@ -60,9 +60,7 @@ class AddDomainDialog : DialogFragment() { } companion object { - // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. - @JvmStatic - fun addDomain(urlString: String): AddDomainDialog { + fun addDomain(urlString: String?): AddDomainDialog { // Create an arguments bundle. val argumentsBundle = Bundle() diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt index 7dfeda99..342c3585 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt @@ -155,7 +155,7 @@ class AboutVersionFragment : Fragment() { } // Define the save about version text activity result launcher. It must be defined before `onCreate()` is run or the app will crash. - private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) { fileUri: Uri? -> + private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri: Uri? -> // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back. if (fileUri != null) { try { @@ -199,7 +199,7 @@ class AboutVersionFragment : Fragment() { } // Define the save about version image activity result launcher. It must be defined before `onCreate()` is run or the app will crash. - private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) { fileUri: Uri? -> + private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri: Uri? -> // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back. if (fileUri != null) { // Save the about version image. @@ -320,7 +320,8 @@ class AboutVersionFragment : Fragment() { // Get the Orbot version name if Orbot is installed. val orbot: String = try { - // Store the version name. + // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33. + @Suppress("DEPRECATION") requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed. // Store an empty string. @@ -329,7 +330,8 @@ class AboutVersionFragment : Fragment() { // Get the I2P version name if I2P is installed. val i2p: String = try { - // Store the version name. + // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33. + @Suppress("DEPRECATION") requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName } catch (exception: PackageManager.NameNotFoundException) { // I2P is not installed. // Store an empty string. @@ -338,7 +340,8 @@ class AboutVersionFragment : Fragment() { // Get the OpenKeychain version name if it is installed. val openKeychain: String = try { - // Store the version name. + // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33. + @Suppress("DEPRECATION") requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed. // Store an empty string. @@ -509,7 +512,8 @@ class AboutVersionFragment : Fragment() { // Display the package signature. try { // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes. - // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. + // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. Once the minimum API >= 33, the newer `getPackageInfo()` may be used. + @Suppress("DEPRECATION") @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES) .signatures[0] diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt index 87ee8a64..8ce9e225 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt @@ -80,7 +80,7 @@ class AboutWebViewFragment : Fragment() { } override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - // Inflate the layout. False does not attach the inflated layout as a child of container. The fragment will take care of attaching the root automatically. + // Inflate the layout. The fragment will take care of attaching the root automatically. webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false) // Get a handle for tab WebView. @@ -118,7 +118,7 @@ class AboutWebViewFragment : Fragment() { val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK // Check to see if the app is in night mode. This can be removed once the minimum API >= 33. - if ((Build.VERSION.SDK_INT < 33) && (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) && (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK))) { // The app is in night mode. + if ((Build.VERSION.SDK_INT < 33) && (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The app is in night mode. // Apply the dark WebView theme. @Suppress("DEPRECATION") WebSettingsCompat.setForceDark(tabWebView.settings, WebSettingsCompat.FORCE_DARK_ON) diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java deleted file mode 100644 index 3a216eb1..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java +++ /dev/null @@ -1,1768 +0,0 @@ -/* - * Copyright © 2017-2022 Soren Stoutner . - * - * This file is part of Privacy Browser Android . - * - * Privacy Browser Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privacy Browser Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privacy Browser Android. If not, see . - */ - -package com.stoutner.privacybrowser.fragments; - -import android.annotation.SuppressLint; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.Cursor; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.Editable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextWatcher; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebView; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.CompoundButton; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.ScrollView; -import android.widget.Spinner; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.SwitchCompat; -import androidx.cardview.widget.CardView; -import androidx.core.content.res.ResourcesCompat; -import androidx.fragment.app.Fragment; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.activities.DomainsActivity; -import com.stoutner.privacybrowser.activities.MainWebViewActivity; -import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; - -public class DomainSettingsFragment extends Fragment { - // Initialize the public class constants. - public static final String DATABASE_ID = "database_id"; - public static final String SCROLL_Y = "scroll_y"; - - // Declare the public variables. `databaseId` is public static so it can be accessed from `DomainsActivity`. - public static int databaseId; - - // Declare the class variables. - private int scrollY; - - @Override - public void onCreate(Bundle savedInstanceState) { - // Run the default commands. - super.onCreate(savedInstanceState); - - // Remove the lint warning that `getArguments` might be null. - assert getArguments() != null; - - // Store the database id in `databaseId`. - databaseId = getArguments().getInt(DATABASE_ID); - scrollY = getArguments().getInt(SCROLL_Y); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate `domain_settings_fragment`. `false` does not attach it to the root `container`. - View domainSettingsView = inflater.inflate(R.layout.domain_settings_fragment, container, false); - - // Get handles for the context and the resources. - Resources resources = getResources(); - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Get a handle for the shared preference. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); - - // Store the default settings. - String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value)); - String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)); - boolean defaultXRequestedWithHeader = sharedPreferences.getBoolean(getString(R.string.x_requested_with_header_key), true); - String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value)); - boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); - String defaultWebViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value)); - boolean defaultWideViewport = sharedPreferences.getBoolean("wide_viewport", true); - boolean defaultDisplayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true); - - // Get handles for the views. - ScrollView domainSettingsScrollView = domainSettingsView.findViewById(R.id.domain_settings_scrollview); - EditText domainNameEditText = domainSettingsView.findViewById(R.id.domain_settings_name_edittext); - ImageView javaScriptImageView = domainSettingsView.findViewById(R.id.javascript_imageview); - SwitchCompat javaScriptSwitch = domainSettingsView.findViewById(R.id.javascript_switch); - ImageView cookiesImageView = domainSettingsView.findViewById(R.id.cookies_imageview); - SwitchCompat cookiesSwitch = domainSettingsView.findViewById(R.id.cookies_switch); - ImageView domStorageImageView = domainSettingsView.findViewById(R.id.dom_storage_imageview); - SwitchCompat domStorageSwitch = domainSettingsView.findViewById(R.id.dom_storage_switch); - ImageView formDataImageView = domainSettingsView.findViewById(R.id.form_data_imageview); // The form data views can be remove once the minimum API >= 26. - SwitchCompat formDataSwitch = domainSettingsView.findViewById(R.id.form_data_switch); // The form data views can be remove once the minimum API >= 26. - ImageView easyListImageView = domainSettingsView.findViewById(R.id.easylist_imageview); - SwitchCompat easyListSwitch = domainSettingsView.findViewById(R.id.easylist_switch); - ImageView easyPrivacyImageView = domainSettingsView.findViewById(R.id.easyprivacy_imageview); - SwitchCompat easyPrivacySwitch = domainSettingsView.findViewById(R.id.easyprivacy_switch); - ImageView fanboysAnnoyanceListImageView = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_imageview); - SwitchCompat fanboysAnnoyanceListSwitch = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_switch); - ImageView fanboysSocialBlockingListImageView = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_imageview); - SwitchCompat fanboysSocialBlockingListSwitch = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_switch); - ImageView ultraListImageView = domainSettingsView.findViewById(R.id.ultralist_imageview); - SwitchCompat ultraListSwitch = domainSettingsView.findViewById(R.id.ultralist_switch); - ImageView ultraPrivacyImageView = domainSettingsView.findViewById(R.id.ultraprivacy_imageview); - SwitchCompat ultraPrivacySwitch = domainSettingsView.findViewById(R.id.ultraprivacy_switch); - ImageView blockAllThirdPartyRequestsImageView = domainSettingsView.findViewById(R.id.block_all_third_party_requests_imageview); - SwitchCompat blockAllThirdPartyRequestsSwitch = domainSettingsView.findViewById(R.id.block_all_third_party_requests_switch); - Spinner userAgentSpinner = domainSettingsView.findViewById(R.id.user_agent_spinner); - TextView userAgentTextView = domainSettingsView.findViewById(R.id.user_agent_textview); - EditText customUserAgentEditText = domainSettingsView.findViewById(R.id.custom_user_agent_edittext); - ImageView xRequestedWithHeaderImageView = domainSettingsView.findViewById(R.id.x_requested_with_header_imageview); - Spinner xRequestedWithHeaderSpinner = domainSettingsView.findViewById(R.id.x_requested_with_header_spinner); - TextView xRequestedWithHeaderTextView = domainSettingsView.findViewById(R.id.x_requested_with_header_textview); - TextView xRequestedWithHeaderExplanationTextView = domainSettingsView.findViewById(R.id.x_requested_with_header_explanation_textview); - Spinner fontSizeSpinner = domainSettingsView.findViewById(R.id.font_size_spinner); - TextView defaultFontSizeTextView = domainSettingsView.findViewById(R.id.default_font_size_textview); - EditText customFontSizeEditText = domainSettingsView.findViewById(R.id.custom_font_size_edittext); - ImageView swipeToRefreshImageView = domainSettingsView.findViewById(R.id.swipe_to_refresh_imageview); - Spinner swipeToRefreshSpinner = domainSettingsView.findViewById(R.id.swipe_to_refresh_spinner); - TextView swipeToRefreshTextView = domainSettingsView.findViewById(R.id.swipe_to_refresh_textview); - ImageView webViewThemeImageView = domainSettingsView.findViewById(R.id.webview_theme_imageview); - Spinner webViewThemeSpinner = domainSettingsView.findViewById(R.id.webview_theme_spinner); - TextView webViewThemeTextView = domainSettingsView.findViewById(R.id.webview_theme_textview); - ImageView wideViewportImageView = domainSettingsView.findViewById(R.id.wide_viewport_imageview); - Spinner wideViewportSpinner = domainSettingsView.findViewById(R.id.wide_viewport_spinner); - TextView wideViewportTextView = domainSettingsView.findViewById(R.id.wide_viewport_textview); - ImageView displayWebpageImagesImageView = domainSettingsView.findViewById(R.id.display_webpage_images_imageview); - Spinner displayWebpageImagesSpinner = domainSettingsView.findViewById(R.id.display_webpage_images_spinner); - TextView displayImagesTextView = domainSettingsView.findViewById(R.id.display_webpage_images_textview); - ImageView pinnedSslCertificateImageView = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_imageview); - SwitchCompat pinnedSslCertificateSwitch = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_switch); - CardView savedSslCardView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_cardview); - LinearLayout savedSslCertificateLinearLayout = domainSettingsView.findViewById(R.id.saved_ssl_certificate_linearlayout); - RadioButton savedSslCertificateRadioButton = domainSettingsView.findViewById(R.id.saved_ssl_certificate_radiobutton); - TextView savedSslIssuedToCNameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_cname); - TextView savedSslIssuedToONameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_oname); - TextView savedSslIssuedToUNameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_uname); - TextView savedSslIssuedByCNameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_cname); - TextView savedSslIssuedByONameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_oname); - TextView savedSslIssuedByUNameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_uname); - TextView savedSslStartDateTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_start_date); - TextView savedSslEndDateTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_end_date); - CardView currentSslCardView = domainSettingsView.findViewById(R.id.current_website_certificate_cardview); - LinearLayout currentWebsiteCertificateLinearLayout = domainSettingsView.findViewById(R.id.current_website_certificate_linearlayout); - RadioButton currentWebsiteCertificateRadioButton = domainSettingsView.findViewById(R.id.current_website_certificate_radiobutton); - TextView currentSslIssuedToCNameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_cname); - TextView currentSslIssuedToONameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_oname); - TextView currentSslIssuedToUNameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_uname); - TextView currentSslIssuedByCNameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_cname); - TextView currentSslIssuedByONameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_oname); - TextView currentSslIssuedByUNameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_uname); - TextView currentSslStartDateTextView = domainSettingsView.findViewById(R.id.current_website_certificate_start_date); - TextView currentSslEndDateTextView = domainSettingsView.findViewById(R.id.current_website_certificate_end_date); - TextView noCurrentWebsiteCertificateTextView = domainSettingsView.findViewById(R.id.no_current_website_certificate); - ImageView pinnedIpAddressesImageView = domainSettingsView.findViewById(R.id.pinned_ip_addresses_imageview); - SwitchCompat pinnedIpAddressesSwitch = domainSettingsView.findViewById(R.id.pinned_ip_addresses_switch); - CardView savedIpAddressesCardView = domainSettingsView.findViewById(R.id.saved_ip_addresses_cardview); - LinearLayout savedIpAddressesLinearLayout = domainSettingsView.findViewById(R.id.saved_ip_addresses_linearlayout); - RadioButton savedIpAddressesRadioButton = domainSettingsView.findViewById(R.id.saved_ip_addresses_radiobutton); - TextView savedIpAddressesTextView = domainSettingsView.findViewById(R.id.saved_ip_addresses_textview); - CardView currentIpAddressesCardView = domainSettingsView.findViewById(R.id.current_ip_addresses_cardview); - LinearLayout currentIpAddressesLinearLayout = domainSettingsView.findViewById(R.id.current_ip_addresses_linearlayout); - RadioButton currentIpAddressesRadioButton = domainSettingsView.findViewById(R.id.current_ip_addresses_radiobutton); - TextView currentIpAddressesTextView = domainSettingsView.findViewById(R.id.current_ip_addresses_textview); - - // Setup the pinned labels. - String cNameLabel = getString(R.string.common_name) + " "; - String oNameLabel = getString(R.string.organization) + " "; - String uNameLabel = getString(R.string.organizational_unit) + " "; - String startDateLabel = getString(R.string.start_date) + " "; - String endDateLabel = getString(R.string.end_date) + " "; - - // Initialize the database handler. - DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(requireContext()); - - // Get the database cursor for this ID and move it to the first row. - Cursor domainCursor = domainsDatabaseHelper.getCursorForId(databaseId); - domainCursor.moveToFirst(); - - // Save the cursor entries as variables. - String domainNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)); - int javaScriptInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)); - int cookiesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)); - int domStorageInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)); - int formDataInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)); // Form data can be remove once the minimum API >= 26. - int easyListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)); - int easyPrivacyInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)); - int fanboysAnnoyanceListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)); - int fanboysSocialBlockingListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)); - int ultraListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)); - int ultraPrivacyInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)); - int blockAllThirdPartyRequestsInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)); - String currentUserAgentName = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT)); - int xRequestedWithHeaderInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER)); - int fontSizeInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE)); - int swipeToRefreshInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); - int webViewThemeInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME)); - int wideViewportInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT)); - int displayImagesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES)); - int pinnedSslCertificateInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)); - String savedSslIssuedToCNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); - String savedSslIssuedToONameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); - String savedSslIssuedToUNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); - String savedSslIssuedByCNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); - String savedSslIssuedByONameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); - String savedSslIssuedByUNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); - int pinnedIpAddressesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)); - String savedIpAddresses = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES)); - - // Initialize the saved SSL certificate date variables. - Date savedSslStartDate = null; - Date savedSslEndDate = null; - - // Only get the saved SSL certificate dates from the cursor if they are not set to `0`. - if (domainCursor.getLong(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)) != 0) { - savedSslStartDate = new Date(domainCursor.getLong(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE))); - } - - if (domainCursor.getLong(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)) != 0) { - savedSslEndDate = new Date(domainCursor.getLong(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE))); - } - - // Create array adapters for the spinners. - ArrayAdapter translatedUserAgentArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.translated_domain_settings_user_agent_names, R.layout.spinner_item); - ArrayAdapter xRequestedWithHeaderArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.x_requested_with_header_array, R.layout.spinner_item); - ArrayAdapter fontSizeArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.font_size_array, R.layout.spinner_item); - ArrayAdapter swipeToRefreshArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.swipe_to_refresh_array, R.layout.spinner_item); - ArrayAdapter webViewThemeArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.webview_theme_array, R.layout.spinner_item); - ArrayAdapter wideViewportArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.wide_viewport_array, R.layout.spinner_item); - ArrayAdapter displayImagesArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.display_webpage_images_array, R.layout.spinner_item); - - // Set the drop down view resource on the spinners. - translatedUserAgentArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); - xRequestedWithHeaderArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); - fontSizeArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); - swipeToRefreshArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); - webViewThemeArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); - wideViewportArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); - displayImagesArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); - - // Set the array adapters for the spinners. - userAgentSpinner.setAdapter(translatedUserAgentArrayAdapter); - xRequestedWithHeaderSpinner.setAdapter(xRequestedWithHeaderArrayAdapter); - fontSizeSpinner.setAdapter(fontSizeArrayAdapter); - swipeToRefreshSpinner.setAdapter(swipeToRefreshArrayAdapter); - webViewThemeSpinner.setAdapter(webViewThemeArrayAdapter); - wideViewportSpinner.setAdapter(wideViewportArrayAdapter); - displayWebpageImagesSpinner.setAdapter(displayImagesArrayAdapter); - - // Create a spannable string builder for each TextView that needs multiple colors of text. - SpannableStringBuilder savedSslIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslIssuedToCNameString); - SpannableStringBuilder savedSslIssuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + savedSslIssuedToONameString); - SpannableStringBuilder savedSslIssuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + savedSslIssuedToUNameString); - SpannableStringBuilder savedSslIssuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslIssuedByCNameString); - SpannableStringBuilder savedSslIssuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + savedSslIssuedByONameString); - SpannableStringBuilder savedSslIssuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + savedSslIssuedByUNameString); - - // Initialize the spannable string builders for the saved SSL certificate dates. - SpannableStringBuilder savedSslStartDateStringBuilder; - SpannableStringBuilder savedSslEndDateStringBuilder; - - // Leave the SSL certificate dates empty if they are `null`. - if (savedSslStartDate == null) { - savedSslStartDateStringBuilder = new SpannableStringBuilder(startDateLabel); - } else { - savedSslStartDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslStartDate)); - } - - if (savedSslEndDate == null) { - savedSslEndDateStringBuilder = new SpannableStringBuilder(endDateLabel); - } else { - savedSslEndDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslEndDate)); - } - - // Create the color spans. - final ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text)); - final ForegroundColorSpan redColorSpan = new ForegroundColorSpan(requireContext().getColor(R.color.red_text)); - - // Set the domain name from the the database cursor. - domainNameEditText.setText(domainNameString); - - // Update the certificates' `Common Name` color when the domain name text changes. - domainNameEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // Do nothing. - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // Do nothing. - } - - @Override - public void afterTextChanged(Editable s) { - // Get the new domain name. - String newDomainName = domainNameEditText.getText().toString(); - - // Check the saved SSL certificate against the new domain name. - boolean savedSslMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, savedSslIssuedToCNameString); - - // Create a `SpannableStringBuilder` for the saved certificate `Common Name`. - SpannableStringBuilder savedSslCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslIssuedToCNameString); - - // Format the saved certificate `Common Name` color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if (savedSslMatchesNewDomainName) { - savedSslCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { - savedSslCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), savedSslCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Update the saved SSL issued to CName text view. - savedSslIssuedToCNameTextView.setText(savedSslCNameStringBuilder); - - // Update the current website certificate if it exists. - if (DomainsActivity.sslIssuedToCName != null) { - // Check the current website certificate against the new domain name. - boolean currentSslMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, DomainsActivity.sslIssuedToCName); - - // Create a `SpannableStringBuilder` for the current website certificate `Common Name`. - SpannableStringBuilder currentSslCNameStringBuilder = new SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedToCName); - - // Format the current certificate `Common Name` color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if (currentSslMatchesNewDomainName) { - currentSslCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentSslCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { - currentSslCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), currentSslCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Update the current SSL issued to CName text view. - currentSslIssuedToCNameTextView.setText(currentSslCNameStringBuilder); - } - } - }); - - // Set the switch positions. - javaScriptSwitch.setChecked(javaScriptInt == 1); - cookiesSwitch.setChecked(cookiesInt == 1); - domStorageSwitch.setChecked(domStorageInt == 1); - formDataSwitch.setChecked(formDataInt == 1); // Form data can be removed once the minimum API >= 26. - easyListSwitch.setChecked(easyListInt == 1); - easyPrivacySwitch.setChecked(easyPrivacyInt == 1); - fanboysAnnoyanceListSwitch.setChecked(fanboysAnnoyanceListInt == 1); - fanboysSocialBlockingListSwitch.setChecked(fanboysSocialBlockingListInt == 1); - ultraListSwitch.setChecked(ultraListInt == 1); - ultraPrivacySwitch.setChecked(ultraPrivacyInt == 1); - blockAllThirdPartyRequestsSwitch.setChecked(blockAllThirdPartyRequestsInt == 1); - pinnedSslCertificateSwitch.setChecked(pinnedSslCertificateInt == 1); - pinnedIpAddressesSwitch.setChecked(pinnedIpAddressesInt == 1); - - // Set the switch icon colors. - cookiesImageView.setSelected(cookiesInt == 1); - domStorageImageView.setSelected(domStorageInt == 1); - formDataImageView.setSelected(formDataInt == 1); // Form data can be removed once the minimum API >= 26. - easyListImageView.setSelected(easyListInt == 1); - easyPrivacyImageView.setSelected(easyPrivacyInt == 1); - fanboysAnnoyanceListImageView.setSelected(fanboysAnnoyanceListInt == 1); - fanboysSocialBlockingListImageView.setSelected(fanboysSocialBlockingListInt == 1); - ultraListImageView.setSelected(ultraListInt == 1); - ultraPrivacyImageView.setSelected(ultraPrivacyInt == 1); - blockAllThirdPartyRequestsImageView.setSelected(blockAllThirdPartyRequestsInt == 1); - pinnedSslCertificateImageView.setSelected(pinnedSslCertificateInt == 1); - pinnedIpAddressesImageView.setSelected(pinnedIpAddressesInt == 1); - - // Set the JavaScript icon. - if (javaScriptInt == 1) - javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.javascript_enabled, null)); - else - javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.privacy_mode, null)); - - // Set the DOM storage switch status based on the JavaScript status. - domStorageSwitch.setEnabled(javaScriptInt == 1); - - // Set the DOM storage icon ghosted status based on the JavaScript status. - domStorageImageView.setEnabled(javaScriptInt == 1); - - // Set the form data visibility. Form data can be removed once the minimum API >= 26. - if (Build.VERSION.SDK_INT >= 26) { - // Hide the form data image view and switch. - formDataImageView.setVisibility(View.GONE); - formDataSwitch.setVisibility(View.GONE); - } - - // Set Fanboy's Social Blocking List switch status based on the Annoyance List status. - fanboysSocialBlockingListSwitch.setEnabled(fanboysAnnoyanceListInt == 0); - - // Set the Social Blocking List icon ghosted status based on the Annoyance List status. - fanboysSocialBlockingListImageView.setEnabled(fanboysAnnoyanceListInt == 0); - - // Inflated a WebView to get the default user agent. - // `@SuppressLint("InflateParams")` removes the warning about using `null` as the `ViewGroup`, which in this case makes sense because the bare WebView should not be displayed on the screen. - @SuppressLint("InflateParams") View bareWebViewLayout = inflater.inflate(R.layout.bare_webview, null, false); - WebView bareWebView = bareWebViewLayout.findViewById(R.id.bare_webview); - final String webViewDefaultUserAgentString = bareWebView.getSettings().getUserAgentString(); - - // Get a handle for the user agent array adapter. This array does not contain the `System default` entry. - ArrayAdapter userAgentNamesArray = ArrayAdapter.createFromResource(requireContext(), R.array.user_agent_names, R.layout.spinner_item); - - // Get the positions of the user agent and the default user agent. - int userAgentArrayPosition = userAgentNamesArray.getPosition(currentUserAgentName); - int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); - - // Get a handle for the user agent data array. This array does not contain the `System default` entry. - String[] userAgentDataArray = resources.getStringArray(R.array.user_agent_data); - - // Set the user agent text. - if (currentUserAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent. - // Set the user agent according to the system default. - switch (defaultUserAgentArrayPosition) { - case MainWebViewActivity.UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list. - // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. - userAgentTextView.setText(defaultUserAgentName); - break; - - case MainWebViewActivity.SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: - // Display the `WebView` default user agent. - userAgentTextView.setText(webViewDefaultUserAgentString); - break; - - case MainWebViewActivity.SETTINGS_CUSTOM_USER_AGENT: - // Display the custom user agent. - userAgentTextView.setText(defaultCustomUserAgentString); - break; - - default: - // Get the user agent string from the user agent data array. - userAgentTextView.setText(userAgentDataArray[defaultUserAgentArrayPosition]); - } - } else if (userAgentArrayPosition == MainWebViewActivity.UNRECOGNIZED_USER_AGENT || currentUserAgentName.equals(getString(R.string.custom_user_agent))) { - // A custom user agent is stored in the current user agent name. The second check is necessary in case the user did not change the default custom text. - // Set the user agent spinner to `Custom user agent`. - userAgentSpinner.setSelection(MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT); - - // Hide the user agent TextView. - userAgentTextView.setVisibility(View.GONE); - - // Show the custom user agent EditText and set the current user agent name as the text. - customUserAgentEditText.setVisibility(View.VISIBLE); - customUserAgentEditText.setText(currentUserAgentName); - } else { // The user agent name contains one of the canonical user agents. - // Set the user agent spinner selection. The spinner has one more entry at the beginning than the user agent data array, so the position must be incremented. - userAgentSpinner.setSelection(userAgentArrayPosition + 1); - - // Show the user agent TextView. - userAgentTextView.setVisibility(View.VISIBLE); - - // Hide the custom user agent EditText. - customUserAgentEditText.setVisibility(View.GONE); - - // Set the user agent text. - if (userAgentArrayPosition == MainWebViewActivity.DOMAINS_WEBVIEW_DEFAULT_USER_AGENT) { // The WebView default user agent is selected. - // Display the WebView default user agent. - userAgentTextView.setText(webViewDefaultUserAgentString); - } else { // A user agent besides the default is selected. - // Get the user agent string from the user agent data array. The spinner has one more entry at the beginning than the user agent data array, so the position must be incremented. - userAgentTextView.setText(userAgentDataArray[userAgentArrayPosition + 1]); - } - } - - // Open the user agent spinner when the text view is clicked. - userAgentTextView.setOnClickListener((View v) -> { - // Open the user agent spinner. - userAgentSpinner.performClick(); - }); - - // Select the X-Requested-With header selection in the spinner. - xRequestedWithHeaderSpinner.setSelection(xRequestedWithHeaderInt); - - // Set the X-Requested-With header text. - if (defaultXRequestedWithHeader) - xRequestedWithHeaderTextView.setText(xRequestedWithHeaderArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED)); - else - xRequestedWithHeaderTextView.setText(xRequestedWithHeaderArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED)); - - // Set the X-Requested-With header icon and text view settings. - switch (xRequestedWithHeaderInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Set the icon color. - xRequestedWithHeaderImageView.setSelected(defaultXRequestedWithHeader); - - // Show the X-Requested-With header text view. - xRequestedWithHeaderTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.ENABLED: - // Set the icon color. - xRequestedWithHeaderImageView.setSelected(true); - - // Hide the X-Requested-With header text view. - xRequestedWithHeaderTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DISABLED: - // Set the icon color. - xRequestedWithHeaderImageView.setSelected(false); - - // Hide the X-Requested-With header text view. - xRequestedWithHeaderTextView.setVisibility(View.GONE); - break; - } - - // Open the X-Requested-With header spinner when the text view is clicked. - xRequestedWithHeaderTextView.setOnClickListener((View v) -> { - // Open the X-Requested-With header spinner. - xRequestedWithHeaderSpinner.performClick(); - }); - - // Open the X-Requested-With header spinner when the explanation text view is clicked. - xRequestedWithHeaderExplanationTextView.setOnClickListener((View v) -> { - // Open the X-Requested header spinner. - xRequestedWithHeaderSpinner.performClick(); - }); - - // Display the font size settings. - if (fontSizeInt == 0) { // `0` is the code for system default font size. - // Set the font size to the system default - fontSizeSpinner.setSelection(0); - - // Show the default font size text view. - defaultFontSizeTextView.setVisibility(View.VISIBLE); - - // Hide the custom font size edit text. - customFontSizeEditText.setVisibility(View.GONE); - - // Set the default font size as the text of the custom font size edit text. This way, if the user switches to custom it will already be populated. - customFontSizeEditText.setText(defaultFontSizeString); - } else { // A custom font size is selected. - // Set the spinner to the custom font size. - fontSizeSpinner.setSelection(1); - - // Hide the default font size text view. - defaultFontSizeTextView.setVisibility(View.GONE); - - // Show the custom font size edit text. - customFontSizeEditText.setVisibility(View.GONE); - - // Set the custom font size. - customFontSizeEditText.setText(String.valueOf(fontSizeInt)); - } - - // Initialize the default font size percentage string. - String defaultFontSizePercentageString = defaultFontSizeString + "%"; - - // Set the default font size text in the text view. - defaultFontSizeTextView.setText(defaultFontSizePercentageString); - - // Open the font size spinner when the text view is clicked. - defaultFontSizeTextView.setOnClickListener((View v) -> { - // Open the user agent spinner. - fontSizeSpinner.performClick(); - }); - - // Select the swipe to refresh selection in the spinner. - swipeToRefreshSpinner.setSelection(swipeToRefreshInt); - - // Set the swipe to refresh text. - if (defaultSwipeToRefresh) - swipeToRefreshTextView.setText(swipeToRefreshArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED)); - else - swipeToRefreshTextView.setText(swipeToRefreshArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED)); - - // Set the swipe to refresh icon and text view settings. - switch (swipeToRefreshInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Set the icon color. - swipeToRefreshImageView.setSelected(defaultSwipeToRefresh); - - // Show the swipe to refresh text view. - swipeToRefreshTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.ENABLED: - // Set the icon color. - swipeToRefreshImageView.setSelected(true); - - // Hide the swipe to refresh text view. - swipeToRefreshTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DISABLED: - // Set the icon color. - swipeToRefreshImageView.setSelected(false); - - // Hide the swipe to refresh text view. - swipeToRefreshTextView.setVisibility(View.GONE); - break; - } - - // Open the swipe to refresh spinner when the text view is clicked. - swipeToRefreshTextView.setOnClickListener((View v) -> { - // Open the swipe to refresh spinner. - swipeToRefreshSpinner.performClick(); - }); - - // Get the WebView theme string arrays. - String[] webViewThemeStringArray = resources.getStringArray(R.array.webview_theme_array); - String[] webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values); - - // Define an app WebView theme entry number. - int appWebViewThemeEntryNumber; - - // Get the WebView theme entry number that matches the current WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. - if (defaultWebViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. - // Store the default WebView theme entry number. - appWebViewThemeEntryNumber = 1; - } else if (defaultWebViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. - // Store the default WebView theme entry number. - appWebViewThemeEntryNumber = 2; - } else { // The system default theme is selected. - // Store the default WebView theme entry number. - appWebViewThemeEntryNumber = 0; - } - - // Select the WebView theme in the spinner. - webViewThemeSpinner.setSelection(webViewThemeInt); - - // Set the WebView theme text. - if (appWebViewThemeEntryNumber == DomainsDatabaseHelper.SYSTEM_DEFAULT) { // The app WebView theme is system default. - // Set the text according to the current UI theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - webViewThemeTextView.setText(webViewThemeStringArray[DomainsDatabaseHelper.LIGHT_THEME]); - } else { - webViewThemeTextView.setText(webViewThemeStringArray[DomainsDatabaseHelper.DARK_THEME]); - } - } else { // The app WebView theme is not system default. - // Set the text according to the app WebView theme. - webViewThemeTextView.setText(webViewThemeStringArray[appWebViewThemeEntryNumber]); - } - - // Set the WebView theme icon and text visibility. - switch (webViewThemeInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: // The domain WebView theme is system default. - // Set the icon according to the app WebView theme. - switch (appWebViewThemeEntryNumber) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: // The default WebView theme is system default. - // Set the icon color. - webViewThemeImageView.setSelected(currentThemeStatus == Configuration.UI_MODE_NIGHT_NO); - break; - - case DomainsDatabaseHelper.LIGHT_THEME: // the default WebView theme is light. - // Set the icon color. - webViewThemeImageView.setSelected(true); - break; - - case DomainsDatabaseHelper.DARK_THEME: // the default WebView theme is dark. - // Set the icon color. - webViewThemeImageView.setSelected(false); - break; - } - - // Show the WebView theme text view. - webViewThemeTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.LIGHT_THEME: // The domain WebView theme is light. - // Set the icon color. - webViewThemeImageView.setSelected(true); - - // Hide the WebView theme text view. - webViewThemeTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DARK_THEME: // The domain WebView theme is dark. - // Set the icon color. - webViewThemeImageView.setSelected(false); - - // Hide the WebView theme text view. - webViewThemeTextView.setVisibility(View.GONE); - break; - } - - // Open the WebView theme spinner when the text view is clicked. - webViewThemeTextView.setOnClickListener((View v) -> { - // Open the WebView theme spinner. - webViewThemeSpinner.performClick(); - }); - - // Select the wide viewport in the spinner. - wideViewportSpinner.setSelection(wideViewportInt); - - // Set the default wide viewport text. - if (defaultWideViewport) - wideViewportTextView.setText(wideViewportArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED)); - else - wideViewportTextView.setText(wideViewportArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED)); - - // Set the wide viewport icon and text view settings. - switch (wideViewportInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Set the icon color. - wideViewportImageView.setSelected(defaultWideViewport); - - // Show the wide viewport text view. - wideViewportTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.ENABLED: - // Set the icon color. - wideViewportImageView.setSelected(true); - - // Hide the wide viewport text view. - wideViewportTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DISABLED: - // Set the icon color. - wideViewportImageView.setSelected(false); - - // Hide the wide viewport text view. - wideViewportTextView.setVisibility(View.GONE); - break; - } - - // Open the wide viewport spinner when the text view is clicked. - wideViewportTextView.setOnClickListener((View view) -> { - // Open the wide viewport spinner. - wideViewportSpinner.performClick(); - }); - - // Display the website images mode in the spinner. - displayWebpageImagesSpinner.setSelection(displayImagesInt); - - // Set the default display images text. - if (defaultDisplayWebpageImages) { - displayImagesTextView.setText(displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED)); - } else { - displayImagesTextView.setText(displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED)); - } - - // Set the display website images icon and text view settings. - switch (displayImagesInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Set the icon color. - displayWebpageImagesImageView.setSelected(defaultDisplayWebpageImages); - - // Show the display images text view. - displayImagesTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.ENABLED: - // Set the icon color. - displayWebpageImagesImageView.setSelected(true); - - // Hide the display images text view. - displayImagesTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DISABLED: - // Set the icon color. - displayWebpageImagesImageView.setSelected(false); - - // Hide the display images text view. - displayImagesTextView.setVisibility(View.GONE); - break; - } - - // Open the display images spinner when the text view is clicked. - displayImagesTextView.setOnClickListener((View view) -> { - // Open the user agent spinner. - displayWebpageImagesSpinner.performClick(); - }); - - // Store the current date. - Date currentDate = Calendar.getInstance().getTime(); - - // Setup the string builders to display the general certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - savedSslIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), savedSslIssuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - savedSslIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), savedSslIssuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - savedSslIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslIssuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - savedSslIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), savedSslIssuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - savedSslIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), savedSslIssuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Check the certificate Common Name against the domain name. - boolean savedSslCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, savedSslIssuedToCNameString); - - // Format the issued to Common Name color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if (savedSslCommonNameMatchesDomainName) { - savedSslIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { - savedSslIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), savedSslIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if ((savedSslStartDate != null) && savedSslStartDate.after(currentDate)) { // The certificate start date is in the future. - savedSslStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), savedSslStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // The certificate start date is in the past. - savedSslStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), savedSslStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if ((savedSslEndDate != null) && savedSslEndDate.before(currentDate)) { // The certificate end date is in the past. - savedSslEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), savedSslEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // The certificate end date is in the future. - savedSslEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), savedSslEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Display the saved website SSL certificate strings. - savedSslIssuedToCNameTextView.setText(savedSslIssuedToCNameStringBuilder); - savedSslIssuedToONameTextView.setText(savedSslIssuedToONameStringBuilder); - savedSslIssuedToUNameTextView.setText(savedSslIssuedToUNameStringBuilder); - savedSslIssuedByCNameTextView.setText(savedSslIssuedByCNameStringBuilder); - savedSslIssuedByONameTextView.setText(savedSslIssuedByONameStringBuilder); - savedSslIssuedByUNameTextView.setText(savedSslIssuedByUNameStringBuilder); - savedSslStartDateTextView.setText(savedSslStartDateStringBuilder); - savedSslEndDateTextView.setText(savedSslEndDateStringBuilder); - - // Populate the current website SSL certificate if there is one. - if (DomainsActivity.sslIssuedToCName != null) { - // Get dates from the raw long values. - Date currentSslStartDate = new Date(DomainsActivity.sslStartDateLong); - Date currentSslEndDate = new Date(DomainsActivity.sslEndDateLong); - - // Create a spannable string builder for each text view that needs multiple colors of text. - SpannableStringBuilder currentSslIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedToCName); - SpannableStringBuilder currentSslIssuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + DomainsActivity.sslIssuedToOName); - SpannableStringBuilder currentSslIssuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + DomainsActivity.sslIssuedToUName); - SpannableStringBuilder currentSslIssuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedByCName); - SpannableStringBuilder currentSslIssuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + DomainsActivity.sslIssuedByOName); - SpannableStringBuilder currentSslIssuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + DomainsActivity.sslIssuedByUName); - SpannableStringBuilder currentSslStartDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG) - .format(currentSslStartDate)); - SpannableStringBuilder currentSslEndDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG) - .format(currentSslEndDate)); - - // Setup the string builders to display the general certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - currentSslIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), currentSslIssuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - currentSslIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), currentSslIssuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - currentSslIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentSslIssuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - currentSslIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), currentSslIssuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - currentSslIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), currentSslIssuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Check the certificate Common Name against the domain name. - boolean currentSslCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, DomainsActivity.sslIssuedToCName); - - // Format the issued to Common Name color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if (currentSslCommonNameMatchesDomainName) { - currentSslIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentSslIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { - currentSslIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), currentSslIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if (currentSslStartDate.after(currentDate)) { // The certificate start date is in the future. - currentSslStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), currentSslStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // The certificate start date is in the past. - currentSslStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), currentSslStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if (currentSslEndDate.before(currentDate)) { // The certificate end date is in the past. - currentSslEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), currentSslEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // The certificate end date is in the future. - currentSslEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), currentSslEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Display the current website SSL certificate strings. - currentSslIssuedToCNameTextView.setText(currentSslIssuedToCNameStringBuilder); - currentSslIssuedToONameTextView.setText(currentSslIssuedToONameStringBuilder); - currentSslIssuedToUNameTextView.setText(currentSslIssuedToUNameStringBuilder); - currentSslIssuedByCNameTextView.setText(currentSslIssuedByCNameStringBuilder); - currentSslIssuedByONameTextView.setText(currentSslIssuedByONameStringBuilder); - currentSslIssuedByUNameTextView.setText(currentSslIssuedByUNameStringBuilder); - currentSslStartDateTextView.setText(currentSslStartDateStringBuilder); - currentSslEndDateTextView.setText(currentSslEndDateStringBuilder); - } - - // Set the initial display status of the SSL certificates card views. - if (pinnedSslCertificateSwitch.isChecked()) { // An SSL certificate is pinned. - // Set the visibility of the saved SSL certificate. - if (savedSslIssuedToCNameString == null) { - savedSslCardView.setVisibility(View.GONE); - } else { - savedSslCardView.setVisibility(View.VISIBLE); - } - - // Set the visibility of the current website SSL certificate. - if (DomainsActivity.sslIssuedToCName == null) { // There is no current SSL certificate. - // Hide the SSL certificate. - currentSslCardView.setVisibility(View.GONE); - - // Show the instruction. - noCurrentWebsiteCertificateTextView.setVisibility(View.VISIBLE); - } else { // There is a current SSL certificate. - // Show the SSL certificate. - currentSslCardView.setVisibility(View.VISIBLE); - - // Hide the instruction. - noCurrentWebsiteCertificateTextView.setVisibility(View.GONE); - } - - // Set the status of the radio buttons and the card view backgrounds. - if (savedSslCardView.getVisibility() == View.VISIBLE) { // The saved SSL certificate is displayed. - // Check the saved SSL certificate radio button. - savedSslCertificateRadioButton.setChecked(true); - - // Uncheck the current website SSL certificate radio button. - currentWebsiteCertificateRadioButton.setChecked(false); - - // Darken the background of the current website SSL certificate linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - } else if (currentSslCardView.getVisibility() == View.VISIBLE) { // The saved SSL certificate is hidden but the current website SSL certificate is visible. - // Check the current website SSL certificate radio button. - currentWebsiteCertificateRadioButton.setChecked(true); - - // Uncheck the saved SSL certificate radio button. - savedSslCertificateRadioButton.setChecked(false); - } else { // Neither SSL certificate is visible. - // Uncheck both radio buttons. - savedSslCertificateRadioButton.setChecked(false); - currentWebsiteCertificateRadioButton.setChecked(false); - } - } else { // An SSL certificate is not pinned. - // Hide the SSl certificates and instructions. - savedSslCardView.setVisibility(View.GONE); - currentSslCardView.setVisibility(View.GONE); - noCurrentWebsiteCertificateTextView.setVisibility(View.GONE); - - // Uncheck the radio buttons. - savedSslCertificateRadioButton.setChecked(false); - currentWebsiteCertificateRadioButton.setChecked(false); - } - - // Populate the saved and current IP addresses. - savedIpAddressesTextView.setText(savedIpAddresses); - currentIpAddressesTextView.setText(DomainsActivity.currentIpAddresses); - - // Set the initial display status of the IP addresses card views. - if (pinnedIpAddressesSwitch.isChecked()) { // IP addresses are pinned. - // Set the visibility of the saved IP addresses. - if (savedIpAddresses == null) { // There are no saved IP addresses. - savedIpAddressesCardView.setVisibility(View.GONE); - } else { // There are saved IP addresses. - savedIpAddressesCardView.setVisibility(View.VISIBLE); - } - - // Set the visibility of the current IP addresses. - currentIpAddressesCardView.setVisibility(View.VISIBLE); - - // Set the status of the radio buttons and the card view backgrounds. - if (savedIpAddressesCardView.getVisibility() == View.VISIBLE) { // The saved IP addresses are displayed. - // Check the saved IP addresses radio button. - savedIpAddressesRadioButton.setChecked(true); - - // Uncheck the current IP addresses radio button. - currentIpAddressesRadioButton.setChecked(false); - - // Darken the background of the current IP addresses linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - } else { // The saved IP addresses are hidden. - // Check the current IP addresses radio button. - currentIpAddressesRadioButton.setChecked(true); - - // Uncheck the saved IP addresses radio button. - savedIpAddressesRadioButton.setChecked(false); - } - } else { // IP addresses are not pinned. - // Hide the IP addresses card views. - savedIpAddressesCardView.setVisibility(View.GONE); - currentIpAddressesCardView.setVisibility(View.GONE); - - // Uncheck the radio buttons. - savedIpAddressesRadioButton.setChecked(false); - currentIpAddressesRadioButton.setChecked(false); - } - - - // Set the JavaScript switch listener. - javaScriptSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the JavaScript icon. - if (isChecked) - javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.javascript_enabled, null)); - else - javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.privacy_mode, null)); - - // Set the DOM storage switch status. - domStorageSwitch.setEnabled(isChecked); - - // Set the DOM storage ghosted icon status. - domStorageImageView.setEnabled(isChecked); - }); - - // Set the cookies switch listener. - cookiesSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - cookiesImageView.setSelected(isChecked); - }); - - // Set the DOM Storage switch listener. - domStorageSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - domStorageImageView.setSelected(isChecked); - }); - - // Set the form data switch listener. It can be removed once the minimum API >= 26. - if (Build.VERSION.SDK_INT < 26) { - formDataSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - formDataImageView.setSelected(isChecked); - }); - } - - // Set the EasyList switch listener. - easyListSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - easyListImageView.setSelected(isChecked); - }); - - // Set the EasyPrivacy switch listener. - easyPrivacySwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - easyPrivacyImageView.setSelected(isChecked); - }); - - // Set the Fanboy's Annoyance List switch listener. - fanboysAnnoyanceListSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - fanboysAnnoyanceListImageView.setSelected(isChecked); - - - // Set Fanboy's Social Blocking List switch position. - fanboysSocialBlockingListSwitch.setEnabled(!isChecked); - - // Set the Social Blocking List icon ghosted status. - fanboysSocialBlockingListImageView.setEnabled(!isChecked); - }); - - // Set the Fanboy's Social Blocking List switch listener. - fanboysSocialBlockingListSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - fanboysSocialBlockingListImageView.setSelected(isChecked); - }); - - // Set the UltraList switch listener. - ultraListSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - ultraListImageView.setSelected(isChecked); - }); - - // Set the UltraPrivacy switch listener. - ultraPrivacySwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - ultraPrivacyImageView.setSelected(isChecked); - }); - - // Set the block all third-party requests switch listener. - blockAllThirdPartyRequestsSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - blockAllThirdPartyRequestsImageView.setSelected(isChecked); - }); - - // Set the user agent spinner listener. - userAgentSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Set the new user agent. - switch (position) { - case MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT: - // Show the user agent TextView. - userAgentTextView.setVisibility(View.VISIBLE); - - // Hide the custom user agent EditText. - customUserAgentEditText.setVisibility(View.GONE); - - // Set the user text. - switch (defaultUserAgentArrayPosition) { - case MainWebViewActivity.UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list. - // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. - userAgentTextView.setText(defaultUserAgentName); - break; - - case MainWebViewActivity.SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: - // Display the `WebView` default user agent. - userAgentTextView.setText(webViewDefaultUserAgentString); - break; - - case MainWebViewActivity.SETTINGS_CUSTOM_USER_AGENT: - // Display the custom user agent. - userAgentTextView.setText(defaultCustomUserAgentString); - break; - - default: - // Get the user agent string from the user agent data array. - userAgentTextView.setText(userAgentDataArray[defaultUserAgentArrayPosition]); - } - break; - - case MainWebViewActivity.DOMAINS_WEBVIEW_DEFAULT_USER_AGENT: - // Show the user agent TextView and set the text. - userAgentTextView.setVisibility(View.VISIBLE); - userAgentTextView.setText(webViewDefaultUserAgentString); - - // Hide the custom user agent EditTex. - customUserAgentEditText.setVisibility(View.GONE); - break; - - case MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT: - // Hide the user agent TextView. - userAgentTextView.setVisibility(View.GONE); - - // Show the custom user agent EditText and set the current user agent name as the text. - customUserAgentEditText.setVisibility(View.VISIBLE); - customUserAgentEditText.setText(currentUserAgentName); - break; - - default: - // Show the user agent TextView and set the text from the user agent data array, which has one less entry than the spinner, so the position must be decremented. - userAgentTextView.setVisibility(View.VISIBLE); - userAgentTextView.setText(userAgentDataArray[position - 1]); - - // Hide `customUserAgentEditText`. - customUserAgentEditText.setVisibility(View.GONE); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // Do nothing. - } - }); - - // Set the X-Requested-With header spinner listener. - xRequestedWithHeaderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update the icon and the visibility of the text view. - switch (position) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Set the icon color. - xRequestedWithHeaderImageView.setSelected(defaultXRequestedWithHeader); - - // Show the X-Requested-With header text view. - xRequestedWithHeaderTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.ENABLED: - // Set the icon color. - xRequestedWithHeaderImageView.setSelected(true); - - // Hide the X-Requested-With header text view. - xRequestedWithHeaderTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DISABLED: - // Set the icon color. - xRequestedWithHeaderImageView.setSelected(false); - - // Hide the X-Requested-With header text view. - xRequestedWithHeaderTextView.setVisibility(View.GONE); - break; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // Do nothing. - } - }); - - // Set the font size spinner listener. - fontSizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update the font size display options. - if (position == 0) { // The system default font size has been selected. - // Show the default font size text view. - defaultFontSizeTextView.setVisibility(View.VISIBLE); - - // Hide the custom font size edit text. - customFontSizeEditText.setVisibility(View.GONE); - } else { // A custom font size has been selected. - // Hide the default font size text view. - defaultFontSizeTextView.setVisibility(View.GONE); - - // Show the custom font size edit text. - customFontSizeEditText.setVisibility(View.VISIBLE); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // Do nothing. - } - }); - - // Set the swipe to refresh spinner listener. - swipeToRefreshSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update the icon and the visibility of the text view. - switch (position) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Set the icon color. - swipeToRefreshImageView.setSelected(defaultSwipeToRefresh); - - // Show the swipe to refresh text view. - swipeToRefreshTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.ENABLED: - // Set the icon color. - swipeToRefreshImageView.setSelected(true); - - // Hide the swipe to refresh text view. - swipeToRefreshTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DISABLED: - // Set the icon color. - swipeToRefreshImageView.setSelected(false); - - // Hide the swipe to refresh text view. - swipeToRefreshTextView.setVisibility(View.GONE); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // Do nothing. - } - }); - - // Set the WebView theme spinner listener. - webViewThemeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update the icon and the visibility of the WebView theme text view. - switch (position) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: // the domain WebView theme is system default. - // Set the icon according to the app WebView theme. - switch (appWebViewThemeEntryNumber) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: // The default WebView theme is system default. - // Set the icon color. - webViewThemeImageView.setSelected(currentThemeStatus == Configuration.UI_MODE_NIGHT_NO); - break; - - case DomainsDatabaseHelper.LIGHT_THEME: // The default WebView theme is light. - // Set the icon color. - webViewThemeImageView.setSelected(true); - break; - - case DomainsDatabaseHelper.DARK_THEME: // The default WebView theme is dark. - // Set the icon. - webViewThemeImageView.setSelected(false); - break; - } - - // Show the WebView theme text view. - webViewThemeTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.LIGHT_THEME: // The domain WebView theme is light. - // Set the icon color. - webViewThemeImageView.setSelected(true); - - // Hide the WebView theme text view. - webViewThemeTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DARK_THEME: // The domain WebView theme is dark. - // Set the icon color. - webViewThemeImageView.setSelected(false); - - // Hide the WebView theme text view. - webViewThemeTextView.setVisibility(View.GONE); - break; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // Do nothing. - } - }); - - // Set the wide viewport spinner listener. - wideViewportSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update the icon and the visibility of the wide viewport text view. - switch (position) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Set the icon color. - wideViewportImageView.setSelected(defaultWideViewport); - - // Show the wide viewport text view. - wideViewportTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.ENABLED: - // Set the icon color. - wideViewportImageView.setSelected(true); - - // Hide the wide viewport text view. - wideViewportTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DISABLED: - // Set the icon color. - wideViewportImageView.setSelected(false); - - // Hid ethe wide viewport text view. - wideViewportTextView.setVisibility(View.GONE); - break; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // Do nothing. - } - }); - - // Set the display webpage images spinner listener. - displayWebpageImagesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update the icon and the visibility of the display images text view. - switch (position) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Set the icon color. - displayWebpageImagesImageView.setSelected(defaultDisplayWebpageImages); - - // Show the display images text view. - displayImagesTextView.setVisibility(View.VISIBLE); - break; - - case DomainsDatabaseHelper.ENABLED: - // Set the icon color. - displayWebpageImagesImageView.setSelected(true); - - // Hide the display images text view. - displayImagesTextView.setVisibility(View.GONE); - break; - - case DomainsDatabaseHelper.DISABLED: - // Set the icon color. - displayWebpageImagesImageView.setSelected(false); - - // Hide the display images text view. - displayImagesTextView.setVisibility(View.GONE); - break; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - // Do nothing. - } - }); - - // Set the pinned SSL certificate switch listener. - pinnedSslCertificateSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - pinnedSslCertificateImageView.setSelected(isChecked); - - // Update the views. - if (isChecked) { // SSL certificate pinning is enabled. - // Update the visibility of the saved SSL certificate. - if (savedSslIssuedToCNameString == null) { - savedSslCardView.setVisibility(View.GONE); - } else { - savedSslCardView.setVisibility(View.VISIBLE); - } - - // Update the visibility of the current website SSL certificate. - if (DomainsActivity.sslIssuedToCName == null) { - // Hide the SSL certificate. - currentSslCardView.setVisibility(View.GONE); - - // Show the instruction. - noCurrentWebsiteCertificateTextView.setVisibility(View.VISIBLE); - } else { - // Show the SSL certificate. - currentSslCardView.setVisibility(View.VISIBLE); - - // Hide the instruction. - noCurrentWebsiteCertificateTextView.setVisibility(View.GONE); - } - - // Set the status of the radio buttons. - if (savedSslCardView.getVisibility() == View.VISIBLE) { // The saved SSL certificate is displayed. - // Check the saved SSL certificate radio button. - savedSslCertificateRadioButton.setChecked(true); - - // Uncheck the current website SSL certificate radio button. - currentWebsiteCertificateRadioButton.setChecked(false); - - // Set the background of the saved SSL certificate linear layout to be transparent. - savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the current website SSL certificate linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - - // Scroll to the current website SSL certificate card. - savedSslCardView.getParent().requestChildFocus(savedSslCardView, savedSslCardView); - } else if (currentSslCardView.getVisibility() == View.VISIBLE) { // The saved SSL certificate is hidden but the current website SSL certificate is visible. - // Check the current website SSL certificate radio button. - currentWebsiteCertificateRadioButton.setChecked(true); - - // Uncheck the saved SSL certificate radio button. - savedSslCertificateRadioButton.setChecked(false); - - // Set the background of the current website SSL certificate linear layout to be transparent. - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the saved SSL certificate linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - - // Scroll to the current website SSL certificate card. - currentSslCardView.getParent().requestChildFocus(currentSslCardView, currentSslCardView); - } else { // Neither SSL certificate is visible. - // Uncheck both radio buttons. - savedSslCertificateRadioButton.setChecked(false); - currentWebsiteCertificateRadioButton.setChecked(false); - - // Scroll to the current website SSL certificate card. - noCurrentWebsiteCertificateTextView.getParent().requestChildFocus(noCurrentWebsiteCertificateTextView, noCurrentWebsiteCertificateTextView); - } - } else { // SSL certificate pinning is disabled. - // Hide the SSl certificates and instructions. - savedSslCardView.setVisibility(View.GONE); - currentSslCardView.setVisibility(View.GONE); - noCurrentWebsiteCertificateTextView.setVisibility(View.GONE); - - // Uncheck the radio buttons. - savedSslCertificateRadioButton.setChecked(false); - currentWebsiteCertificateRadioButton.setChecked(false); - } - }); - - savedSslCardView.setOnClickListener((View view) -> { - // Check the saved SSL certificate radio button. - savedSslCertificateRadioButton.setChecked(true); - - // Uncheck the current website SSL certificate radio button. - currentWebsiteCertificateRadioButton.setChecked(false); - - // Set the background of the saved SSL certificate linear layout to be transparent. - savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the current website SSL certificate linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - }); - - savedSslCertificateRadioButton.setOnClickListener((View view) -> { - // Check the saved SSL certificate radio button. - savedSslCertificateRadioButton.setChecked(true); - - // Uncheck the current website SSL certificate radio button. - currentWebsiteCertificateRadioButton.setChecked(false); - - // Set the background of the saved SSL certificate linear layout to be transparent. - savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the current website SSL certificate linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - }); - - currentSslCardView.setOnClickListener((View view) -> { - // Check the current website SSL certificate radio button. - currentWebsiteCertificateRadioButton.setChecked(true); - - // Uncheck the saved SSL certificate radio button. - savedSslCertificateRadioButton.setChecked(false); - - // Set the background of the current website SSL certificate linear layout to be transparent. - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the saved SSL certificate linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - }); - - currentWebsiteCertificateRadioButton.setOnClickListener((View view) -> { - // Check the current website SSL certificate radio button. - currentWebsiteCertificateRadioButton.setChecked(true); - - // Uncheck the saved SSL certificate radio button. - savedSslCertificateRadioButton.setChecked(false); - - // Set the background of the current website SSL certificate linear layout to be transparent. - currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the saved SSL certificate linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - }); - - // Set the pinned IP addresses switch listener. - pinnedIpAddressesSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { - // Update the icon color. - pinnedIpAddressesImageView.setSelected(isChecked); - - // Update the views. - if (isChecked) { // IP addresses pinning is enabled. - // Update the visibility of the saved IP addresses card view. - if (savedIpAddresses == null) { // There are no saved IP addresses. - savedIpAddressesCardView.setVisibility(View.GONE); - } else { // There are saved IP addresses. - savedIpAddressesCardView.setVisibility(View.VISIBLE); - } - - // Show the current IP addresses card view. - currentIpAddressesCardView.setVisibility(View.VISIBLE); - - // Set the status of the radio buttons. - if (savedIpAddressesCardView.getVisibility() == View.VISIBLE) { // The saved IP addresses are visible. - // Check the saved IP addresses radio button. - savedIpAddressesRadioButton.setChecked(true); - - // Uncheck the current IP addresses radio button. - currentIpAddressesRadioButton.setChecked(false); - - // Set the background of the saved IP addresses linear layout to be transparent. - savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the current IP addresses linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - } else { // The saved IP addresses are not visible. - // Check the current IP addresses radio button. - currentIpAddressesRadioButton.setChecked(true); - - // Uncheck the saved IP addresses radio button. - savedIpAddressesRadioButton.setChecked(false); - - // Set the background of the current IP addresses linear layout to be transparent. - currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the saved IP addresses linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - } - - // Scroll to the bottom of the card views. - currentIpAddressesCardView.getParent().requestChildFocus(currentIpAddressesCardView, currentIpAddressesCardView); - } else { // IP addresses pinning is disabled. - // Hide the IP addresses card views. - savedIpAddressesCardView.setVisibility(View.GONE); - currentIpAddressesCardView.setVisibility(View.GONE); - - // Uncheck the radio buttons. - savedIpAddressesRadioButton.setChecked(false); - currentIpAddressesRadioButton.setChecked(false); - } - }); - - savedIpAddressesCardView.setOnClickListener((View view) -> { - // Check the saved IP addresses radio button. - savedIpAddressesRadioButton.setChecked(true); - - // Uncheck the current website IP addresses radio button. - currentIpAddressesRadioButton.setChecked(false); - - // Set the background of the saved IP addresses linear layout to be transparent. - savedIpAddressesLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the current IP addresses linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - }); - - savedIpAddressesRadioButton.setOnClickListener((View view) -> { - // Check the saved IP addresses radio button. - savedIpAddressesRadioButton.setChecked(true); - - // Uncheck the current website IP addresses radio button. - currentIpAddressesRadioButton.setChecked(false); - - // Set the background of the saved IP addresses linear layout to be transparent. - savedIpAddressesLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the current IP addresses linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - }); - - currentIpAddressesCardView.setOnClickListener((View view) -> { - // Check the current IP addresses radio button. - currentIpAddressesRadioButton.setChecked(true); - - // Uncheck the saved IP addresses radio button. - savedIpAddressesRadioButton.setChecked(false); - - // Set the background of the current IP addresses linear layout to be transparent. - currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the saved IP addresses linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - }); - - currentIpAddressesRadioButton.setOnClickListener((View view) -> { - // Check the current IP addresses radio button. - currentIpAddressesRadioButton.setChecked(true); - - // Uncheck the saved IP addresses radio button. - savedIpAddressesRadioButton.setChecked(false); - - // Set the background of the current IP addresses linear layout to be transparent. - currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent); - - // Darken the background of the saved IP addresses linear layout according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33); - } else { - savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11); - } - }); - - // Set the scroll Y. - domainSettingsScrollView.post(() -> domainSettingsScrollView.setScrollY(scrollY)); - - // Return the domain settings view. - return domainSettingsView; - } - - private boolean checkDomainNameAgainstCertificate(String domainName, String certificateCommonName) { - // Initialize `domainNamesMatch`. - boolean domainNamesMatch = false; - - // Check various wildcard permutations if `domainName` and `certificateCommonName` are not empty. - // `noinspection ConstantCondition` removes Android Studio's incorrect lint warning that `domainName` can never be `null`. - if ((domainName != null) && (certificateCommonName != null)) { - // Check if the domains match. - if (domainName.equals(certificateCommonName)) { - domainNamesMatch = true; - } - - // If `domainName` starts with a wildcard, check the base domain against all the subdomains of `certificateCommonName`. - if (!domainNamesMatch && domainName.startsWith("*.") && (domainName.length() > 2)) { - // Remove the initial `*.`. - String baseDomainName = domainName.substring(2); - - // Setup a copy of `certificateCommonName` to test subdomains. - String certificateCommonNameSubdomain = certificateCommonName; - - // Check all the subdomains in `certificateCommonNameSubdomain` against `baseDomainName`. - while (!domainNamesMatch && certificateCommonNameSubdomain.contains(".")) { // Stop checking if we know that `domainNamesMatch` is `true` or if we run out of `.`. - // Test the `certificateCommonNameSubdomain` against `baseDomainName`. - if (certificateCommonNameSubdomain.equals(baseDomainName)) { - domainNamesMatch = true; - } - - // Strip out the lowest subdomain of `certificateCommonNameSubdomain`. - try { - certificateCommonNameSubdomain = certificateCommonNameSubdomain.substring(certificateCommonNameSubdomain.indexOf(".") + 1); - } catch (IndexOutOfBoundsException e) { // `certificateCommonNameSubdomain` ends with `.`. - certificateCommonNameSubdomain = ""; - } - } - } - - // If `certificateCommonName` starts with a wildcard, check the base common name against all the subdomains of `domainName`. - if (!domainNamesMatch && certificateCommonName.startsWith("*.") && (certificateCommonName.length() > 2)) { - // Remove the initial `*.`. - String baseCertificateCommonName = certificateCommonName.substring(2); - - // Setup a copy of `domainName` to test subdomains. - String domainNameSubdomain = domainName; - - // Check all the subdomains in `domainNameSubdomain` against `baseCertificateCommonName`. - while (!domainNamesMatch && domainNameSubdomain.contains(".") && (domainNameSubdomain.length() > 2)) { - // Test the `domainNameSubdomain` against `baseCertificateCommonName`. - if (domainNameSubdomain.equals(baseCertificateCommonName)) { - domainNamesMatch = true; - } - - // Strip out the lowest subdomain of `domainNameSubdomain`. - try { - domainNameSubdomain = domainNameSubdomain.substring(domainNameSubdomain.indexOf(".") + 1); - } catch (IndexOutOfBoundsException e) { // `domainNameSubdomain` ends with `.`. - domainNameSubdomain = ""; - } - } - } - - // If both names start with a wildcard, check if the root of one contains the root of the other. - if (!domainNamesMatch && domainName.startsWith("*.") && (domainName.length() > 2) && certificateCommonName.startsWith("*.") && (certificateCommonName.length() > 2)) { - // Remove the wildcards. - String rootDomainName = domainName.substring(2); - String rootCertificateCommonName = certificateCommonName.substring(2); - - // Check if one name ends with the contents of the other. If so, there will be overlap in the their wildcard subdomains. - if (rootDomainName.endsWith(rootCertificateCommonName) || rootCertificateCommonName.endsWith(rootDomainName)) { - domainNamesMatch = true; - } - } - } - - return domainNamesMatch; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.kt b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.kt new file mode 100644 index 00000000..52739a4a --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.kt @@ -0,0 +1,1610 @@ +/* + * Copyright 2017-2022 Soren Stoutner . + * + * This file is part of Privacy Browser Android . + * + * Privacy Browser Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser Android. If not, see . + */ + +package com.stoutner.privacybrowser.fragments + +import android.annotation.SuppressLint +import android.content.res.Configuration +import android.os.Build +import android.os.Bundle +import android.text.Editable +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.TextWatcher +import android.text.style.ForegroundColorSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebView +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.CompoundButton +import android.widget.EditText +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RadioButton +import android.widget.ScrollView +import android.widget.Spinner +import android.widget.TextView + +import androidx.appcompat.widget.SwitchCompat +import androidx.cardview.widget.CardView +import androidx.core.content.res.ResourcesCompat +import androidx.fragment.app.Fragment +import androidx.preference.PreferenceManager + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.activities.DomainsActivity +import com.stoutner.privacybrowser.activities.MainWebViewActivity +import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper + +import java.lang.IndexOutOfBoundsException +import java.text.DateFormat +import java.util.Calendar +import java.util.Date + +class DomainSettingsFragment : Fragment() { + // Define the class variables. + private var scrollY = 0 + + companion object { + // Define the public constants. + const val DATABASE_ID = "database_id" + const val SCROLL_Y = "scroll_y" + + // Define the public variables. `databaseId` is public so it can be accessed from `DomainsActivity`. + var databaseId = 0 + } + + override fun onCreate(savedInstanceState: Bundle?) { + // Run the default commands. + super.onCreate(savedInstanceState) + + // Store the arguments in class variables. + databaseId = requireArguments().getInt(DATABASE_ID) + scrollY = requireArguments().getInt(SCROLL_Y) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + // Inflate the layout. The fragment will take care of attaching the root automatically. + val domainSettingsView = inflater.inflate(R.layout.domain_settings_fragment, container, false) + + // Get the current theme status. + val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + + // Get a handle for the shared preference. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + + // Store the default settings. + val defaultUserAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value)) + val defaultCustomUserAgentString = sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value)) + val defaultXRequestedWithHeader = sharedPreferences.getBoolean(getString(R.string.x_requested_with_header_key), true) + val defaultFontSizeString = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value)) + val defaultSwipeToRefresh = sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true) + val defaultWebViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value)) + val defaultWideViewport = sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true) + val defaultDisplayWebpageImages = sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true) + + // Get handles for the views. + val domainSettingsScrollView = domainSettingsView.findViewById(R.id.domain_settings_scrollview) + val domainNameEditText = domainSettingsView.findViewById(R.id.domain_settings_name_edittext) + val javaScriptImageView = domainSettingsView.findViewById(R.id.javascript_imageview) + val javaScriptSwitch = domainSettingsView.findViewById(R.id.javascript_switch) + val cookiesImageView = domainSettingsView.findViewById(R.id.cookies_imageview) + val cookiesSwitch = domainSettingsView.findViewById(R.id.cookies_switch) + val domStorageImageView = domainSettingsView.findViewById(R.id.dom_storage_imageview) + val domStorageSwitch = domainSettingsView.findViewById(R.id.dom_storage_switch) + val formDataImageView = domainSettingsView.findViewById(R.id.form_data_imageview) // The form data views can be remove once the minimum API >= 26. + val formDataSwitch = domainSettingsView.findViewById(R.id.form_data_switch) // The form data views can be remove once the minimum API >= 26. + val easyListImageView = domainSettingsView.findViewById(R.id.easylist_imageview) + val easyListSwitch = domainSettingsView.findViewById(R.id.easylist_switch) + val easyPrivacyImageView = domainSettingsView.findViewById(R.id.easyprivacy_imageview) + val easyPrivacySwitch = domainSettingsView.findViewById(R.id.easyprivacy_switch) + val fanboysAnnoyanceListImageView = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_imageview) + val fanboysAnnoyanceListSwitch = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_switch) + val fanboysSocialBlockingListImageView = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_imageview) + val fanboysSocialBlockingListSwitch = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_switch) + val ultraListImageView = domainSettingsView.findViewById(R.id.ultralist_imageview) + val ultraListSwitch = domainSettingsView.findViewById(R.id.ultralist_switch) + val ultraPrivacyImageView = domainSettingsView.findViewById(R.id.ultraprivacy_imageview) + val ultraPrivacySwitch = domainSettingsView.findViewById(R.id.ultraprivacy_switch) + val blockAllThirdPartyRequestsImageView = domainSettingsView.findViewById(R.id.block_all_third_party_requests_imageview) + val blockAllThirdPartyRequestsSwitch = domainSettingsView.findViewById(R.id.block_all_third_party_requests_switch) + val userAgentSpinner = domainSettingsView.findViewById(R.id.user_agent_spinner) + val userAgentTextView = domainSettingsView.findViewById(R.id.user_agent_textview) + val customUserAgentEditText = domainSettingsView.findViewById(R.id.custom_user_agent_edittext) + val xRequestedWithHeaderImageView = domainSettingsView.findViewById(R.id.x_requested_with_header_imageview) + val xRequestedWithHeaderSpinner = domainSettingsView.findViewById(R.id.x_requested_with_header_spinner) + val xRequestedWithHeaderTextView = domainSettingsView.findViewById(R.id.x_requested_with_header_textview) + val xRequestedWithHeaderExplanationTextView = domainSettingsView.findViewById(R.id.x_requested_with_header_explanation_textview) + val fontSizeSpinner = domainSettingsView.findViewById(R.id.font_size_spinner) + val defaultFontSizeTextView = domainSettingsView.findViewById(R.id.default_font_size_textview) + val customFontSizeEditText = domainSettingsView.findViewById(R.id.custom_font_size_edittext) + val swipeToRefreshImageView = domainSettingsView.findViewById(R.id.swipe_to_refresh_imageview) + val swipeToRefreshSpinner = domainSettingsView.findViewById(R.id.swipe_to_refresh_spinner) + val swipeToRefreshTextView = domainSettingsView.findViewById(R.id.swipe_to_refresh_textview) + val webViewThemeImageView = domainSettingsView.findViewById(R.id.webview_theme_imageview) + val webViewThemeSpinner = domainSettingsView.findViewById(R.id.webview_theme_spinner) + val webViewThemeTextView = domainSettingsView.findViewById(R.id.webview_theme_textview) + val wideViewportImageView = domainSettingsView.findViewById(R.id.wide_viewport_imageview) + val wideViewportSpinner = domainSettingsView.findViewById(R.id.wide_viewport_spinner) + val wideViewportTextView = domainSettingsView.findViewById(R.id.wide_viewport_textview) + val displayWebpageImagesImageView = domainSettingsView.findViewById(R.id.display_webpage_images_imageview) + val displayWebpageImagesSpinner = domainSettingsView.findViewById(R.id.display_webpage_images_spinner) + val displayImagesTextView = domainSettingsView.findViewById(R.id.display_webpage_images_textview) + val pinnedSslCertificateImageView = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_imageview) + val pinnedSslCertificateSwitch = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_switch) + val savedSslCardView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_cardview) + val savedSslCertificateLinearLayout = domainSettingsView.findViewById(R.id.saved_ssl_certificate_linearlayout) + val savedSslCertificateRadioButton = domainSettingsView.findViewById(R.id.saved_ssl_certificate_radiobutton) + val savedSslIssuedToCNameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_cname) + val savedSslIssuedToONameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_oname) + val savedSslIssuedToUNameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_uname) + val savedSslIssuedByCNameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_cname) + val savedSslIssuedByONameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_oname) + val savedSslIssuedByUNameTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_uname) + val savedSslStartDateTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_start_date) + val savedSslEndDateTextView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_end_date) + val currentSslCardView = domainSettingsView.findViewById(R.id.current_website_certificate_cardview) + val currentWebsiteCertificateLinearLayout = domainSettingsView.findViewById(R.id.current_website_certificate_linearlayout) + val currentWebsiteCertificateRadioButton = domainSettingsView.findViewById(R.id.current_website_certificate_radiobutton) + val currentSslIssuedToCNameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_cname) + val currentSslIssuedToONameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_oname) + val currentSslIssuedToUNameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_uname) + val currentSslIssuedByCNameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_cname) + val currentSslIssuedByONameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_oname) + val currentSslIssuedByUNameTextView = domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_uname) + val currentSslStartDateTextView = domainSettingsView.findViewById(R.id.current_website_certificate_start_date) + val currentSslEndDateTextView = domainSettingsView.findViewById(R.id.current_website_certificate_end_date) + val noCurrentWebsiteCertificateTextView = domainSettingsView.findViewById(R.id.no_current_website_certificate) + val pinnedIpAddressesImageView = domainSettingsView.findViewById(R.id.pinned_ip_addresses_imageview) + val pinnedIpAddressesSwitch = domainSettingsView.findViewById(R.id.pinned_ip_addresses_switch) + val savedIpAddressesCardView = domainSettingsView.findViewById(R.id.saved_ip_addresses_cardview) + val savedIpAddressesLinearLayout = domainSettingsView.findViewById(R.id.saved_ip_addresses_linearlayout) + val savedIpAddressesRadioButton = domainSettingsView.findViewById(R.id.saved_ip_addresses_radiobutton) + val savedIpAddressesTextView = domainSettingsView.findViewById(R.id.saved_ip_addresses_textview) + val currentIpAddressesCardView = domainSettingsView.findViewById(R.id.current_ip_addresses_cardview) + val currentIpAddressesLinearLayout = domainSettingsView.findViewById(R.id.current_ip_addresses_linearlayout) + val currentIpAddressesRadioButton = domainSettingsView.findViewById(R.id.current_ip_addresses_radiobutton) + val currentIpAddressesTextView = domainSettingsView.findViewById(R.id.current_ip_addresses_textview) + + // Setup the pinned labels. + val cNameLabel = getString(R.string.common_name) + " " + val oNameLabel = getString(R.string.organization) + " " + val uNameLabel = getString(R.string.organizational_unit) + " " + val startDateLabel = getString(R.string.start_date) + " " + val endDateLabel = getString(R.string.end_date) + " " + + // Initialize the database handler. + val domainsDatabaseHelper = DomainsDatabaseHelper(requireContext()) + + // Get the database cursor for this ID. + val domainCursor = domainsDatabaseHelper.getCursorForId(databaseId) + + // Move to the first row. + domainCursor.moveToFirst() + + // Save the cursor entries as variables. + val domainNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)) + val javaScriptInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) + val cookiesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)) + val domStorageInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) + val formDataInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)) // Form data can be remove once the minimum API >= 26. + val easyListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)) + val easyPrivacyInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) + val fanboysAnnoyanceListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) + val fanboysSocialBlockingListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) + val ultraListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)) + val ultraPrivacyInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) + val blockAllThirdPartyRequestsInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) + val currentUserAgentName = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT)) + val xRequestedWithHeaderInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER)) + val fontSizeInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE)) + val swipeToRefreshInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH)) + val webViewThemeInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME)) + val wideViewportInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT)) + val displayImagesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES)) + val pinnedSslCertificateInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) + val savedSslIssuedToCNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)) + val savedSslIssuedToONameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)) + val savedSslIssuedToUNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)) + val savedSslIssuedByCNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)) + val savedSslIssuedByONameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)) + val savedSslIssuedByUNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)) + val pinnedIpAddressesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) + val savedIpAddresses = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES)) + + // Get the SSL dates from the database. + val savedSslStartDateLong = domainCursor.getLong(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)) + val savedSslEndDateLong = domainCursor.getLong(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)) + + // Initialize the saved SSL certificate date variables. + var savedSslStartDate: Date? = null + var savedSslEndDate: Date? = null + + // Only get the saved SSL certificate dates from the cursor if they are not set to `0`. + if (savedSslStartDateLong != 0L) + savedSslStartDate = Date(savedSslStartDateLong) + if (savedSslEndDateLong != 0L) + savedSslEndDate = Date(savedSslEndDateLong) + + // Create array adapters for the spinners. + val translatedUserAgentArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.translated_domain_settings_user_agent_names, R.layout.spinner_item) + val xRequestedWithHeaderArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.x_requested_with_header_array, R.layout.spinner_item) + val fontSizeArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.font_size_array, R.layout.spinner_item) + val swipeToRefreshArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.swipe_to_refresh_array, R.layout.spinner_item) + val webViewThemeArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.webview_theme_array, R.layout.spinner_item) + val wideViewportArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.wide_viewport_array, R.layout.spinner_item) + val displayImagesArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.display_webpage_images_array, R.layout.spinner_item) + + // Set the drop down view resource on the spinners. + translatedUserAgentArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items) + xRequestedWithHeaderArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items) + fontSizeArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items) + swipeToRefreshArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items) + webViewThemeArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items) + wideViewportArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items) + displayImagesArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items) + + // Set the array adapters for the spinners. + userAgentSpinner.adapter = translatedUserAgentArrayAdapter + xRequestedWithHeaderSpinner.adapter = xRequestedWithHeaderArrayAdapter + fontSizeSpinner.adapter = fontSizeArrayAdapter + swipeToRefreshSpinner.adapter = swipeToRefreshArrayAdapter + webViewThemeSpinner.adapter = webViewThemeArrayAdapter + wideViewportSpinner.adapter = wideViewportArrayAdapter + displayWebpageImagesSpinner.adapter = displayImagesArrayAdapter + + // Create a spannable string builder for each TextView that needs multiple colors of text. + val savedSslIssuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + savedSslIssuedToCNameString) + val savedSslIssuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + savedSslIssuedToONameString) + val savedSslIssuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + savedSslIssuedToUNameString) + val savedSslIssuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + savedSslIssuedByCNameString) + val savedSslIssuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + savedSslIssuedByONameString) + val savedSslIssuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + savedSslIssuedByUNameString) + + // Create the date spannable string builders. + val savedSslStartDateStringBuilder: SpannableStringBuilder = if (savedSslStartDate == null) + SpannableStringBuilder(startDateLabel) + else + SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslStartDate)) + + val savedSslEndDateStringBuilder: SpannableStringBuilder = if (savedSslEndDate == null) + SpannableStringBuilder(endDateLabel) + else + SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslEndDate)) + + // Create the color spans. + val blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text)) + val redColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.red_text)) + + // Set the domain name from the the database cursor. + domainNameEditText.setText(domainNameString) + + // Update the certificates' Common Name color when the domain name text changes. + domainNameEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + // Do nothing. + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + // Do nothing. + } + + override fun afterTextChanged(s: Editable) { + // Get the new domain name. + val newDomainName = domainNameEditText.text.toString() + + // Check the saved SSL certificate against the new domain name. + val savedSslMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, savedSslIssuedToCNameString) + + // Create a spannable string builder for the saved certificate's Common Name. + val savedSslCNameStringBuilder = SpannableStringBuilder(cNameLabel + savedSslIssuedToCNameString) + + // Format the saved certificate's Common Name color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (savedSslMatchesNewDomainName) { + savedSslCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, savedSslCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } else { + savedSslCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, savedSslCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } + + // Update the saved SSL issued to CName text view. + savedSslIssuedToCNameTextView.text = savedSslCNameStringBuilder + + // Update the current website certificate if it exists. + if (DomainsActivity.sslIssuedToCName != null) { + // Check the current website certificate against the new domain name. + val currentSslMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, DomainsActivity.sslIssuedToCName) + + // Create a spannable string builder for the current website certificate's Common Name. + val currentSslCNameStringBuilder = SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedToCName) + + // Format the current certificate Common Name color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (currentSslMatchesNewDomainName) { + currentSslCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, currentSslCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } else { + currentSslCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, currentSslCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } + + // Update the current SSL issued to CName text view. + currentSslIssuedToCNameTextView.text = currentSslCNameStringBuilder + } + } + }) + + // Set the switch positions. + javaScriptSwitch.isChecked = javaScriptInt == 1 + cookiesSwitch.isChecked = cookiesInt == 1 + domStorageSwitch.isChecked = domStorageInt == 1 + formDataSwitch.isChecked = formDataInt == 1 // Form data can be removed once the minimum API >= 26. + easyListSwitch.isChecked = easyListInt == 1 + easyPrivacySwitch.isChecked = easyPrivacyInt == 1 + fanboysAnnoyanceListSwitch.isChecked = fanboysAnnoyanceListInt == 1 + fanboysSocialBlockingListSwitch.isChecked = fanboysSocialBlockingListInt == 1 + ultraListSwitch.isChecked = ultraListInt == 1 + ultraPrivacySwitch.isChecked = ultraPrivacyInt == 1 + blockAllThirdPartyRequestsSwitch.isChecked = blockAllThirdPartyRequestsInt == 1 + pinnedSslCertificateSwitch.isChecked = pinnedSslCertificateInt == 1 + pinnedIpAddressesSwitch.isChecked = pinnedIpAddressesInt == 1 + + // Set the switch icon colors. + cookiesImageView.isSelected = cookiesInt == 1 + domStorageImageView.isSelected = domStorageInt == 1 + formDataImageView.isSelected = formDataInt == 1 // Form data can be removed once the minimum API >= 26. + easyListImageView.isSelected = easyListInt == 1 + easyPrivacyImageView.isSelected = easyPrivacyInt == 1 + fanboysAnnoyanceListImageView.isSelected = fanboysAnnoyanceListInt == 1 + fanboysSocialBlockingListImageView.isSelected = fanboysSocialBlockingListInt == 1 + ultraListImageView.isSelected = ultraListInt == 1 + ultraPrivacyImageView.isSelected = ultraPrivacyInt == 1 + blockAllThirdPartyRequestsImageView.isSelected = blockAllThirdPartyRequestsInt == 1 + pinnedSslCertificateImageView.isSelected = pinnedSslCertificateInt == 1 + pinnedIpAddressesImageView.isSelected = pinnedIpAddressesInt == 1 + + // Set the JavaScript icon. + if (javaScriptInt == 1) + javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.javascript_enabled, null)) + else + javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.privacy_mode, null)) + + // Set the DOM storage switch status based on the JavaScript status. + domStorageSwitch.isEnabled = (javaScriptInt == 1) + + // Set the DOM storage icon ghosted status based on the JavaScript status. + domStorageImageView.isEnabled = (javaScriptInt == 1) + + // Set the form data visibility. Form data can be removed once the minimum API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + // Hide the form data image view and switch. + formDataImageView.visibility = View.GONE + formDataSwitch.visibility = View.GONE + } + + // Set Fanboy's Social Blocking List switch status based on the Annoyance List status. + fanboysSocialBlockingListSwitch.isEnabled = (fanboysAnnoyanceListInt == 0) + + // Set the Social Blocking List icon ghosted status based on the Annoyance List status. + fanboysSocialBlockingListImageView.isEnabled = (fanboysAnnoyanceListInt == 0) + + // Inflated a WebView to get the default user agent. + // `@SuppressLint("InflateParams")` removes the warning about using `null` as the `ViewGroup`, which in this case makes sense because the bare WebView should not be displayed on the screen. + @SuppressLint("InflateParams") val bareWebViewLayout = inflater.inflate(R.layout.bare_webview, null, false) + val bareWebView = bareWebViewLayout.findViewById(R.id.bare_webview) + val webViewDefaultUserAgentString = bareWebView.settings.userAgentString + + // Get a handle for the user agent array adapter. This array does not contain the `System default` entry. + val userAgentNamesArray = ArrayAdapter.createFromResource(requireContext(), R.array.user_agent_names, R.layout.spinner_item) + + // Get the positions of the user agent and the default user agent. + val userAgentArrayPosition = userAgentNamesArray.getPosition(currentUserAgentName) + val defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName) + + // Get a handle for the user agent data array. This array does not contain the `System default` entry. + val userAgentDataArray = resources.getStringArray(R.array.user_agent_data) + + // Set the user agent text. + if (currentUserAgentName == getString(R.string.system_default_user_agent)) { // Use the system default user agent. + // Set the user agent according to the system default. + when (defaultUserAgentArrayPosition) { + // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. + MainWebViewActivity.UNRECOGNIZED_USER_AGENT -> userAgentTextView.text = defaultUserAgentName + + // Display the WebView default user agent. + MainWebViewActivity.SETTINGS_WEBVIEW_DEFAULT_USER_AGENT -> userAgentTextView.text = webViewDefaultUserAgentString + + // Display the custom user agent. + MainWebViewActivity.SETTINGS_CUSTOM_USER_AGENT -> userAgentTextView.text = defaultCustomUserAgentString + + // Get the user agent string from the user agent data array. + else -> userAgentTextView.text = userAgentDataArray[defaultUserAgentArrayPosition] + } + } else if (userAgentArrayPosition == MainWebViewActivity.UNRECOGNIZED_USER_AGENT || currentUserAgentName == getString(R.string.custom_user_agent)) { + // A custom user agent is stored in the current user agent name. The second check is necessary in case the user did not change the default custom text. + // Set the user agent spinner to `Custom user agent`. + userAgentSpinner.setSelection(MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT) + + // Hide the user agent text view. + userAgentTextView.visibility = View.GONE + + // Show the custom user agent edit text and set the current user agent name as the text. + customUserAgentEditText.visibility = View.VISIBLE + customUserAgentEditText.setText(currentUserAgentName) + } else { // The user agent name contains one of the canonical user agents. + // Set the user agent spinner selection. The spinner has one more entry at the beginning than the user agent data array, so the position must be incremented. + userAgentSpinner.setSelection(userAgentArrayPosition + 1) + + // Show the user agent text view. + userAgentTextView.visibility = View.VISIBLE + + // Hide the custom user agent edit text. + customUserAgentEditText.visibility = View.GONE + + // Set the user agent text. + if (userAgentArrayPosition == MainWebViewActivity.DOMAINS_WEBVIEW_DEFAULT_USER_AGENT) { // The WebView default user agent is selected. + // Display the WebView default user agent. + userAgentTextView.text = webViewDefaultUserAgentString + } else { // A user agent besides the default is selected. + // Get the user agent string from the user agent data array. The spinner has one more entry at the beginning than the user agent data array, so the position must be incremented. + userAgentTextView.text = userAgentDataArray[userAgentArrayPosition + 1] + } + } + + // Open the user agent spinner when the text view is clicked. + userAgentTextView.setOnClickListener { userAgentSpinner.performClick() } + + // Select the X-Requested-With header selection in the spinner. + xRequestedWithHeaderSpinner.setSelection(xRequestedWithHeaderInt) + + // Set the X-Requested-With header text. + if (defaultXRequestedWithHeader) + xRequestedWithHeaderTextView.text = xRequestedWithHeaderArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED) + else + xRequestedWithHeaderTextView.text = xRequestedWithHeaderArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED) + + // Set the X-Requested-With header icon and text view settings. + when (xRequestedWithHeaderInt) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + xRequestedWithHeaderImageView.isSelected = defaultXRequestedWithHeader + + // Show the X-Requested-With header text view. + xRequestedWithHeaderTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.ENABLED -> { + // Set the icon color. + xRequestedWithHeaderImageView.isSelected = true + + // Hide the X-Requested-With header text view. + xRequestedWithHeaderTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DISABLED -> { + // Set the icon color. + xRequestedWithHeaderImageView.isSelected = false + + // Hide the X-Requested-With header text view. + xRequestedWithHeaderTextView.visibility = View.GONE + } + } + + // Open the X-Requested-With header spinner when the text view is clicked. + xRequestedWithHeaderTextView.setOnClickListener { xRequestedWithHeaderSpinner.performClick() } + + // Open the X-Requested-With header spinner when the explanation text view is clicked. + xRequestedWithHeaderExplanationTextView.setOnClickListener { xRequestedWithHeaderSpinner.performClick() } + + // Display the font size settings. + if (fontSizeInt == 0) { // `0` is the code for system default font size. + // Set the font size to the system default. + fontSizeSpinner.setSelection(0) + + // Show the default font size text view. + defaultFontSizeTextView.visibility = View.VISIBLE + + // Hide the custom font size edit text. + customFontSizeEditText.visibility = View.GONE + + // Set the default font size as the text of the custom font size edit text. This way, if the user switches to custom it will already be populated. + customFontSizeEditText.setText(defaultFontSizeString) + } else { // A custom font size is selected. + // Set the spinner to the custom font size. + fontSizeSpinner.setSelection(1) + + // Hide the default font size text view. + defaultFontSizeTextView.visibility = View.GONE + + // Show the custom font size edit text. + customFontSizeEditText.visibility = View.GONE + + // Set the custom font size. + customFontSizeEditText.setText(fontSizeInt.toString()) + } + + // Initialize the default font size percentage string. + val defaultFontSizePercentageString = "$defaultFontSizeString%" + + // Set the default font size text in the text view. + defaultFontSizeTextView.text = defaultFontSizePercentageString + + // Open the font size spinner when the text view is clicked. + defaultFontSizeTextView.setOnClickListener { fontSizeSpinner.performClick() } + + // Select the swipe-to-refresh selection in the spinner. + swipeToRefreshSpinner.setSelection(swipeToRefreshInt) + + // Set the swipe-to-refresh text. + if (defaultSwipeToRefresh) + swipeToRefreshTextView.text = swipeToRefreshArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED) + else + swipeToRefreshTextView.text = swipeToRefreshArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED) + + // Set the swipe-to-refresh icon and text view settings. + when (swipeToRefreshInt) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + swipeToRefreshImageView.isSelected = defaultSwipeToRefresh + + // Show the swipe-to-refresh text view. + swipeToRefreshTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.ENABLED -> { + // Set the icon color. + swipeToRefreshImageView.isSelected = true + + // Hide the swipe-to-refresh text view. + swipeToRefreshTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DISABLED -> { + // Set the icon color. + swipeToRefreshImageView.isSelected = false + + // Hide the swipe-to-refresh text view. + swipeToRefreshTextView.visibility = View.GONE + } + } + + // Open the swipe-to-refresh spinner when the text view is clicked. + swipeToRefreshTextView.setOnClickListener { swipeToRefreshSpinner.performClick() } + + // Get the WebView theme string arrays. + val webViewThemeStringArray = resources.getStringArray(R.array.webview_theme_array) + val webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values) + + // Get the WebView theme entry number that matches the current WebView theme. + val appWebViewThemeEntryNumber = when (defaultWebViewTheme) { + webViewThemeEntryValuesStringArray[1] -> { 1 } // The light theme is selected. + webViewThemeEntryValuesStringArray[2] -> { 2 } // The dark theme is selected. + else -> { 0 } // The system default theme is selected. + } + + // Select the WebView theme in the spinner. + webViewThemeSpinner.setSelection(webViewThemeInt) + + // Set the WebView theme text. + if (appWebViewThemeEntryNumber == DomainsDatabaseHelper.SYSTEM_DEFAULT) { // The app WebView theme is system default. + // Set the text according to the current UI theme. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) + webViewThemeTextView.text = webViewThemeStringArray[DomainsDatabaseHelper.LIGHT_THEME] + else + webViewThemeTextView.text = webViewThemeStringArray[DomainsDatabaseHelper.DARK_THEME] + } else { // The app WebView theme is not system default. + // Set the text according to the app WebView theme. + webViewThemeTextView.text = webViewThemeStringArray[appWebViewThemeEntryNumber] + } + + // Set the WebView theme icon and text visibility. + when (webViewThemeInt) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + when (appWebViewThemeEntryNumber) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> webViewThemeImageView.isSelected = (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) + DomainsDatabaseHelper.LIGHT_THEME -> webViewThemeImageView.isSelected = true + DomainsDatabaseHelper.DARK_THEME -> webViewThemeImageView.isSelected = false + } + + // Show the WebView theme text view. + webViewThemeTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.LIGHT_THEME -> { + // Set the icon color. + webViewThemeImageView.isSelected = true + + // Hide the WebView theme text view. + webViewThemeTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DARK_THEME -> { + // Set the icon color. + webViewThemeImageView.isSelected = false + + // Hide the WebView theme text view. + webViewThemeTextView.visibility = View.GONE + } + } + + // Open the WebView theme spinner when the text view is clicked. + webViewThemeTextView.setOnClickListener { webViewThemeSpinner.performClick() } + + // Select the wide viewport in the spinner. + wideViewportSpinner.setSelection(wideViewportInt) + + // Set the default wide viewport text. + if (defaultWideViewport) + wideViewportTextView.text = wideViewportArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED) + else wideViewportTextView.text = wideViewportArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED) + + // Set the wide viewport icon and text view settings. + when (wideViewportInt) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + wideViewportImageView.isSelected = defaultWideViewport + + // Show the wide viewport text view. + wideViewportTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.ENABLED -> { + // Set the icon color. + wideViewportImageView.isSelected = true + + // Hide the wide viewport text view. + wideViewportTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DISABLED -> { + // Set the icon color. + wideViewportImageView.isSelected = false + + // Hide the wide viewport text view. + wideViewportTextView.visibility = View.GONE + } + } + + // Open the wide viewport spinner when the text view is clicked. + wideViewportTextView.setOnClickListener { wideViewportSpinner.performClick() } + + // Display the website images mode in the spinner. + displayWebpageImagesSpinner.setSelection(displayImagesInt) + + // Set the default display images text. + if (defaultDisplayWebpageImages) + displayImagesTextView.text = displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED) + else + displayImagesTextView.text = displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED) + + // Set the display website images icon and text view settings. + when (displayImagesInt) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + displayWebpageImagesImageView.isSelected = defaultDisplayWebpageImages + + // Show the display images text view. + displayImagesTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.ENABLED -> { + // Set the icon color. + displayWebpageImagesImageView.isSelected = true + + // Hide the display images text view. + displayImagesTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DISABLED -> { + // Set the icon color. + displayWebpageImagesImageView.isSelected = false + + // Hide the display images text view. + displayImagesTextView.visibility = View.GONE + } + } + + // Open the display images spinner when the text view is clicked. + displayImagesTextView.setOnClickListener { displayWebpageImagesSpinner.performClick() } + + // Store the current date. + val currentDate = Calendar.getInstance().time + + // Setup the string builders to display the general certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + savedSslIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, savedSslIssuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + savedSslIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, savedSslIssuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + savedSslIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, savedSslIssuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + savedSslIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, savedSslIssuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + savedSslIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, savedSslIssuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Check the certificate Common Name against the domain name. + val savedSslCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, savedSslIssuedToCNameString) + + // Format the issued to Common Name color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (savedSslCommonNameMatchesDomainName) + savedSslIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, savedSslIssuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + else + savedSslIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, savedSslIssuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (savedSslStartDate != null && savedSslStartDate.after(currentDate)) // The certificate start date is in the future. + savedSslStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, savedSslStartDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + else // The certificate start date is in the past. + savedSslStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, savedSslStartDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (savedSslEndDate != null && savedSslEndDate.before(currentDate)) // The certificate end date is in the past. + savedSslEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, savedSslEndDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + else // The certificate end date is in the future. + savedSslEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, savedSslEndDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Display the saved website SSL certificate strings. + savedSslIssuedToCNameTextView.text = savedSslIssuedToCNameStringBuilder + savedSslIssuedToONameTextView.text = savedSslIssuedToONameStringBuilder + savedSslIssuedToUNameTextView.text = savedSslIssuedToUNameStringBuilder + savedSslIssuedByCNameTextView.text = savedSslIssuedByCNameStringBuilder + savedSslIssuedByONameTextView.text = savedSslIssuedByONameStringBuilder + savedSslIssuedByUNameTextView.text = savedSslIssuedByUNameStringBuilder + savedSslStartDateTextView.text = savedSslStartDateStringBuilder + savedSslEndDateTextView.text = savedSslEndDateStringBuilder + + // Populate the current website SSL certificate if there is one. + if (DomainsActivity.sslIssuedToCName != null) { + // Get dates from the raw long values. + val currentSslStartDate = Date(DomainsActivity.sslStartDateLong) + val currentSslEndDate = Date(DomainsActivity.sslEndDateLong) + + // Create a spannable string builder for each text view that needs multiple colors of text. + val currentSslIssuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedToCName) + val currentSslIssuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + DomainsActivity.sslIssuedToOName) + val currentSslIssuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + DomainsActivity.sslIssuedToUName) + val currentSslIssuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedByCName) + val currentSslIssuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + DomainsActivity.sslIssuedByOName) + val currentSslIssuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + DomainsActivity.sslIssuedByUName) + val currentSslStartDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate)) + val currentSslEndDateStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate)) + + // Setup the string builders to display the general certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + currentSslIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, currentSslIssuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + currentSslIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, currentSslIssuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + currentSslIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, currentSslIssuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + currentSslIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, currentSslIssuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + currentSslIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, currentSslIssuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Check the certificate Common Name against the domain name. + val currentSslCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, DomainsActivity.sslIssuedToCName) + + // Format the issued to Common Name color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (currentSslCommonNameMatchesDomainName) + currentSslIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, currentSslIssuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + else + currentSslIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, currentSslIssuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (currentSslStartDate.after(currentDate)) // The certificate start date is in the future. + currentSslStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, currentSslStartDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + else // The certificate start date is in the past. + currentSslStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, currentSslStartDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (currentSslEndDate.before(currentDate)) // The certificate end date is in the past. + currentSslEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, currentSslEndDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + else // The certificate end date is in the future. + currentSslEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, currentSslEndDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Display the current website SSL certificate strings. + currentSslIssuedToCNameTextView.text = currentSslIssuedToCNameStringBuilder + currentSslIssuedToONameTextView.text = currentSslIssuedToONameStringBuilder + currentSslIssuedToUNameTextView.text = currentSslIssuedToUNameStringBuilder + currentSslIssuedByCNameTextView.text = currentSslIssuedByCNameStringBuilder + currentSslIssuedByONameTextView.text = currentSslIssuedByONameStringBuilder + currentSslIssuedByUNameTextView.text = currentSslIssuedByUNameStringBuilder + currentSslStartDateTextView.text = currentSslStartDateStringBuilder + currentSslEndDateTextView.text = currentSslEndDateStringBuilder + } + + // Set the initial display status of the SSL certificates card views. + if (pinnedSslCertificateSwitch.isChecked) { // An SSL certificate is pinned. + // Set the visibility of the saved SSL certificate. + if (savedSslIssuedToCNameString == null) + savedSslCardView.visibility = View.GONE + else + savedSslCardView.visibility = View.VISIBLE + + // Set the visibility of the current website SSL certificate. + if (DomainsActivity.sslIssuedToCName == null) { // There is no current SSL certificate. + // Hide the SSL certificate. + currentSslCardView.visibility = View.GONE + + // Show the instruction. + noCurrentWebsiteCertificateTextView.visibility = View.VISIBLE + } else { // There is a current SSL certificate. + // Show the SSL certificate. + currentSslCardView.visibility = View.VISIBLE + + // Hide the instruction. + noCurrentWebsiteCertificateTextView.visibility = View.GONE + } + + // Set the status of the radio buttons and the card view backgrounds. + if (savedSslCardView.visibility == View.VISIBLE) { // The saved SSL certificate is displayed. + // Check the saved SSL certificate radio button. + savedSslCertificateRadioButton.isChecked = true + + // Uncheck the current website SSL certificate radio button. + currentWebsiteCertificateRadioButton.isChecked = false + + // Darken the background of the current website SSL certificate linear layout. + currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background) + } else if (currentSslCardView.visibility == View.VISIBLE) { // The saved SSL certificate is hidden but the current website SSL certificate is visible. + // Check the current website SSL certificate radio button. + currentWebsiteCertificateRadioButton.isChecked = true + + // Uncheck the saved SSL certificate radio button. + savedSslCertificateRadioButton.isChecked = false + } else { // Neither SSL certificate is visible. + // Uncheck both radio buttons. + savedSslCertificateRadioButton.isChecked = false + currentWebsiteCertificateRadioButton.isChecked = false + } + } else { // An SSL certificate is not pinned. + // Hide the SSl certificates and instructions. + savedSslCardView.visibility = View.GONE + currentSslCardView.visibility = View.GONE + noCurrentWebsiteCertificateTextView.visibility = View.GONE + + // Uncheck the radio buttons. + savedSslCertificateRadioButton.isChecked = false + currentWebsiteCertificateRadioButton.isChecked = false + } + + // Populate the saved and current IP addresses. + savedIpAddressesTextView.text = savedIpAddresses + currentIpAddressesTextView.text = DomainsActivity.currentIpAddresses + + // Set the initial display status of the IP addresses card views. + if (pinnedIpAddressesSwitch.isChecked) { // IP addresses are pinned. + // Set the visibility of the saved IP addresses. + if (savedIpAddresses == null) // There are no saved IP addresses. + savedIpAddressesCardView.visibility = View.GONE + else // There are saved IP addresses. + savedIpAddressesCardView.visibility = View.VISIBLE + + // Set the visibility of the current IP addresses. + currentIpAddressesCardView.visibility = View.VISIBLE + + // Set the status of the radio buttons and the card view backgrounds. + if (savedIpAddressesCardView.visibility == View.VISIBLE) { // The saved IP addresses are displayed. + // Check the saved IP addresses radio button. + savedIpAddressesRadioButton.isChecked = true + + // Uncheck the current IP addresses radio button. + currentIpAddressesRadioButton.isChecked = false + + // Darken the background of the current IP addresses linear layout. + currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background) + } else { // The saved IP addresses are hidden. + // Check the current IP addresses radio button. + currentIpAddressesRadioButton.isChecked = true + + // Uncheck the saved IP addresses radio button. + savedIpAddressesRadioButton.isChecked = false + } + } else { // IP addresses are not pinned. + // Hide the IP addresses card views. + savedIpAddressesCardView.visibility = View.GONE + currentIpAddressesCardView.visibility = View.GONE + + // Uncheck the radio buttons. + savedIpAddressesRadioButton.isChecked = false + currentIpAddressesRadioButton.isChecked = false + } + + + // Set the JavaScript switch listener. + javaScriptSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the JavaScript icon. + if (isChecked) + javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.javascript_enabled, null)) + else + javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.privacy_mode, null)) + + // Set the DOM storage switch status. + domStorageSwitch.isEnabled = isChecked + + // Set the DOM storage ghosted icon status. + domStorageImageView.isEnabled = isChecked + } + + // Set the cookies switch listener. + cookiesSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + cookiesImageView.isSelected = isChecked + } + + // Set the DOM Storage switch listener. + domStorageSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + domStorageImageView.isSelected = isChecked + } + + // Set the form data switch listener. It can be removed once the minimum API >= 26. + if (Build.VERSION.SDK_INT < 26) { + formDataSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + formDataImageView.isSelected = isChecked + } + } + + // Set the EasyList switch listener. + easyListSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + easyListImageView.isSelected = isChecked + } + + // Set the EasyPrivacy switch listener. + easyPrivacySwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + easyPrivacyImageView.isSelected = isChecked + } + + // Set the Fanboy's Annoyance List switch listener. + fanboysAnnoyanceListSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + fanboysAnnoyanceListImageView.isSelected = isChecked + + // Set Fanboy's Social Blocking List switch position. + fanboysSocialBlockingListSwitch.isEnabled = !isChecked + + // Set the Social Blocking List icon ghosted status. + fanboysSocialBlockingListImageView.isEnabled = !isChecked + } + + // Set the Fanboy's Social Blocking List switch listener. + fanboysSocialBlockingListSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + fanboysSocialBlockingListImageView.isSelected = isChecked + } + + // Set the UltraList switch listener. + ultraListSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + ultraListImageView.isSelected = isChecked + } + + // Set the UltraPrivacy switch listener. + ultraPrivacySwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + ultraPrivacyImageView.isSelected = isChecked + } + + // Set the block all third-party requests switch listener. + blockAllThirdPartyRequestsSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + blockAllThirdPartyRequestsImageView.isSelected = isChecked + } + + // Set the user agent spinner listener. + userAgentSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + // Set the new user agent. + when (position) { + MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT -> { + // Show the user agent text view. + userAgentTextView.visibility = View.VISIBLE + + // Hide the custom user agent edit text. + customUserAgentEditText.visibility = View.GONE + + // Set the user text. + when (defaultUserAgentArrayPosition) { + // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. + MainWebViewActivity.UNRECOGNIZED_USER_AGENT -> userAgentTextView.text = defaultUserAgentName + + // Display the `WebView` default user agent. + MainWebViewActivity.SETTINGS_WEBVIEW_DEFAULT_USER_AGENT -> userAgentTextView.text = webViewDefaultUserAgentString + + // Display the custom user agent. + MainWebViewActivity.SETTINGS_CUSTOM_USER_AGENT -> userAgentTextView.text = defaultCustomUserAgentString + + // Get the user agent string from the user agent data array. + else -> userAgentTextView.text = userAgentDataArray[defaultUserAgentArrayPosition] + } + } + + MainWebViewActivity.DOMAINS_WEBVIEW_DEFAULT_USER_AGENT -> { + // Show the user agent text view. + userAgentTextView.visibility = View.VISIBLE + + // Set the user agent text. + userAgentTextView.text = webViewDefaultUserAgentString + + // Hide the custom user agent EditTex. + customUserAgentEditText.visibility = View.GONE + } + + MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT -> { + // Hide the user agent TextView. + userAgentTextView.visibility = View.GONE + + // Show the custom user agent edit text. + customUserAgentEditText.visibility = View.VISIBLE + + // Set the current user agent name as the text. + customUserAgentEditText.setText(currentUserAgentName) + } + + else -> { + // Show the user agent text view. + userAgentTextView.visibility = View.VISIBLE + + // Set the text from the user agent data array, which has one less entry than the spinner, so the position must be decremented. + userAgentTextView.text = userAgentDataArray[position - 1] + + // Hide the custom user agent edit text. + customUserAgentEditText.visibility = View.GONE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Do nothing. + } + } + + // Set the X-Requested-With header spinner listener. + xRequestedWithHeaderSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + // Update the icon and the visibility of the text view. + when (position) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + xRequestedWithHeaderImageView.isSelected = defaultXRequestedWithHeader + + // Show the X-Requested-With header text view. + xRequestedWithHeaderTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.ENABLED -> { + // Set the icon color. + xRequestedWithHeaderImageView.isSelected = true + + // Hide the X-Requested-With header text view. + xRequestedWithHeaderTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DISABLED -> { + // Set the icon color. + xRequestedWithHeaderImageView.isSelected = false + + // Hide the X-Requested-With header text view. + xRequestedWithHeaderTextView.visibility = View.GONE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Do nothing. + } + } + + // Set the font size spinner listener. + fontSizeSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + // Update the font size display options. + if (position == 0) { // The system default font size has been selected. + // Show the default font size text view. + defaultFontSizeTextView.visibility = View.VISIBLE + + // Hide the custom font size edit text. + customFontSizeEditText.visibility = View.GONE + } else { // A custom font size has been selected. + // Hide the default font size text view. + defaultFontSizeTextView.visibility = View.GONE + + // Show the custom font size edit text. + customFontSizeEditText.visibility = View.VISIBLE + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Do nothing. + } + } + + // Set the swipe-to-refresh spinner listener. + swipeToRefreshSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + // Update the icon and the visibility of the text view. + when (position) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + swipeToRefreshImageView.isSelected = defaultSwipeToRefresh + + // Show the swipe-to-refresh text view. + swipeToRefreshTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.ENABLED -> { + // Set the icon color. + swipeToRefreshImageView.isSelected = true + + // Hide the swipe-to-refresh text view. + swipeToRefreshTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DISABLED -> { + // Set the icon color. + swipeToRefreshImageView.isSelected = false + + // Hide the swipe-to-refresh text view. + swipeToRefreshTextView.visibility = View.GONE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Do nothing. + } + } + + // Set the WebView theme spinner listener. + webViewThemeSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + // Update the icon and the visibility of the WebView theme text view. + when (position) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + when (appWebViewThemeEntryNumber) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> webViewThemeImageView.isSelected = (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) + DomainsDatabaseHelper.LIGHT_THEME -> webViewThemeImageView.isSelected = true + DomainsDatabaseHelper.DARK_THEME -> webViewThemeImageView.isSelected = false + } + + // Show the WebView theme text view. + webViewThemeTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.LIGHT_THEME -> { + // Set the icon color. + webViewThemeImageView.isSelected = true + + // Hide the WebView theme text view. + webViewThemeTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DARK_THEME -> { + // Set the icon color. + webViewThemeImageView.isSelected = false + + // Hide the WebView theme text view. + webViewThemeTextView.visibility = View.GONE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Do nothing. + } + } + + // Set the wide viewport spinner listener. + wideViewportSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + // Update the icon and the visibility of the wide viewport text view. + when (position) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + wideViewportImageView.isSelected = defaultWideViewport + + // Show the wide viewport text view. + wideViewportTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.ENABLED -> { + // Set the icon color. + wideViewportImageView.isSelected = true + + // Hide the wide viewport text view. + wideViewportTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DISABLED -> { + // Set the icon color. + wideViewportImageView.isSelected = false + + // Hid ethe wide viewport text view. + wideViewportTextView.visibility = View.GONE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Do nothing. + } + } + + // Set the display webpage images spinner listener. + displayWebpageImagesSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + // Update the icon and the visibility of the display images text view. + when (position) { + DomainsDatabaseHelper.SYSTEM_DEFAULT -> { + // Set the icon color. + displayWebpageImagesImageView.isSelected = defaultDisplayWebpageImages + + // Show the display images text view. + displayImagesTextView.visibility = View.VISIBLE + } + + DomainsDatabaseHelper.ENABLED -> { + // Set the icon color. + displayWebpageImagesImageView.isSelected = true + + // Hide the display images text view. + displayImagesTextView.visibility = View.GONE + } + + DomainsDatabaseHelper.DISABLED -> { + // Set the icon color. + displayWebpageImagesImageView.isSelected = false + + // Hide the display images text view. + displayImagesTextView.visibility = View.GONE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Do nothing. + } + } + + // Set the pinned SSL certificate switch listener. + pinnedSslCertificateSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + pinnedSslCertificateImageView.isSelected = isChecked + + // Update the views. + if (isChecked) { // SSL certificate pinning is enabled. + // Update the visibility of the saved SSL certificate. + if (savedSslIssuedToCNameString == null) + savedSslCardView.visibility = View.GONE + else + savedSslCardView.visibility = View.VISIBLE + + // Update the visibility of the current website SSL certificate. + if (DomainsActivity.sslIssuedToCName == null) { + // Hide the SSL certificate. + currentSslCardView.visibility = View.GONE + + // Show the instruction. + noCurrentWebsiteCertificateTextView.visibility = View.VISIBLE + } else { + // Show the SSL certificate. + currentSslCardView.visibility = View.VISIBLE + + // Hide the instruction. + noCurrentWebsiteCertificateTextView.visibility = View.GONE + } + + // Set the status of the radio buttons. + if (savedSslCardView.visibility == View.VISIBLE) { // The saved SSL certificate is displayed. + // Check the saved SSL certificate radio button. + savedSslCertificateRadioButton.isChecked = true + + // Uncheck the current website SSL certificate radio button. + currentWebsiteCertificateRadioButton.isChecked = false + + // Set the background of the saved SSL certificate linear layout to be transparent. + savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the current website SSL certificate linear layout according to the theme. + currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background) + + // Scroll to the current website SSL certificate card. + savedSslCardView.parent.requestChildFocus(savedSslCardView, savedSslCardView) + } else if (currentSslCardView.visibility == View.VISIBLE) { // The saved SSL certificate is hidden but the current website SSL certificate is visible. + // Check the current website SSL certificate radio button. + currentWebsiteCertificateRadioButton.isChecked = true + + // Uncheck the saved SSL certificate radio button. + savedSslCertificateRadioButton.isChecked = false + + // Set the background of the current website SSL certificate linear layout to be transparent. + currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the saved SSL certificate linear layout according to the theme. + savedSslCertificateLinearLayout.setBackgroundResource(R.color.translucent_background) + + // Scroll to the current website SSL certificate card. + currentSslCardView.parent.requestChildFocus(currentSslCardView, currentSslCardView) + } else { // Neither SSL certificate is visible. + // Uncheck both radio buttons. + savedSslCertificateRadioButton.isChecked = false + currentWebsiteCertificateRadioButton.isChecked = false + + // Scroll to the current website SSL certificate card. + noCurrentWebsiteCertificateTextView.parent.requestChildFocus(noCurrentWebsiteCertificateTextView, noCurrentWebsiteCertificateTextView) + } + } else { // SSL certificate pinning is disabled. + // Hide the SSl certificates and instructions. + savedSslCardView.visibility = View.GONE + currentSslCardView.visibility = View.GONE + noCurrentWebsiteCertificateTextView.visibility = View.GONE + + // Uncheck the radio buttons. + savedSslCertificateRadioButton.isChecked = false + currentWebsiteCertificateRadioButton.isChecked = false + } + } + + // Set the saved SSL card view listener. + savedSslCardView.setOnClickListener { + // Check the saved SSL certificate radio button. + savedSslCertificateRadioButton.isChecked = true + + // Uncheck the current website SSL certificate radio button. + currentWebsiteCertificateRadioButton.isChecked = false + + // Set the background of the saved SSL certificate linear layout to be transparent. + savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the current website SSL certificate linear layout. + currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Set the saved SSL certificate radio button listener. + savedSslCertificateRadioButton.setOnClickListener { + // Check the saved SSL certificate radio button. + savedSslCertificateRadioButton.isChecked = true + + // Uncheck the current website SSL certificate radio button. + currentWebsiteCertificateRadioButton.isChecked = false + + // Set the background of the saved SSL certificate linear layout to be transparent. + savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the current website SSL certificate linear layout. + currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Set the current SSL card view listener. + currentSslCardView.setOnClickListener { + // Check the current website SSL certificate radio button. + currentWebsiteCertificateRadioButton.isChecked = true + + // Uncheck the saved SSL certificate radio button. + savedSslCertificateRadioButton.isChecked = false + + // Set the background of the current website SSL certificate linear layout to be transparent. + currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the saved SSL certificate linear layout. + savedSslCertificateLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Set the current website certificate radio button listener. + currentWebsiteCertificateRadioButton.setOnClickListener { + // Check the current website SSL certificate radio button. + currentWebsiteCertificateRadioButton.isChecked = true + + // Uncheck the saved SSL certificate radio button. + savedSslCertificateRadioButton.isChecked = false + + // Set the background of the current website SSL certificate linear layout to be transparent. + currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the saved SSL certificate linear layout. + savedSslCertificateLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Set the pinned IP addresses switch listener. + pinnedIpAddressesSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // Update the icon color. + pinnedIpAddressesImageView.isSelected = isChecked + + // Update the views. + if (isChecked) { // IP addresses pinning is enabled. + // Update the visibility of the saved IP addresses card view. + if (savedIpAddresses == null) + savedIpAddressesCardView.visibility = View.GONE + else + savedIpAddressesCardView.visibility = View.VISIBLE + + // Show the current IP addresses card view. + currentIpAddressesCardView.visibility = View.VISIBLE + + // Set the status of the radio buttons. + if (savedIpAddressesCardView.visibility == View.VISIBLE) { // The saved IP addresses are visible. + // Check the saved IP addresses radio button. + savedIpAddressesRadioButton.isChecked = true + + // Uncheck the current IP addresses radio button. + currentIpAddressesRadioButton.isChecked = false + + // Set the background of the saved IP addresses linear layout to be transparent. + savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the current IP addresses linear layout. + currentIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background) + } else { // The saved IP addresses are not visible. + // Check the current IP addresses radio button. + currentIpAddressesRadioButton.isChecked = true + + // Uncheck the saved IP addresses radio button. + savedIpAddressesRadioButton.isChecked = false + + // Set the background of the current IP addresses linear layout to be transparent. + currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the saved IP addresses linear layout. + savedIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Scroll to the bottom of the card views. + currentIpAddressesCardView.parent.requestChildFocus(currentIpAddressesCardView, currentIpAddressesCardView) + } else { // IP addresses pinning is disabled. + // Hide the IP addresses card views. + savedIpAddressesCardView.visibility = View.GONE + currentIpAddressesCardView.visibility = View.GONE + + // Uncheck the radio buttons. + savedIpAddressesRadioButton.isChecked = false + currentIpAddressesRadioButton.isChecked = false + } + } + + // Set the saved IP addresses card view listener. + savedIpAddressesCardView.setOnClickListener { + // Check the saved IP addresses radio button. + savedIpAddressesRadioButton.isChecked = true + + // Uncheck the current website IP addresses radio button. + currentIpAddressesRadioButton.isChecked = false + + // Set the background of the saved IP addresses linear layout to be transparent. + savedIpAddressesLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the current IP addresses linear layout. + currentIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Set the saved IP addresses radio button listener. + savedIpAddressesRadioButton.setOnClickListener { + // Check the saved IP addresses radio button. + savedIpAddressesRadioButton.isChecked = true + + // Uncheck the current website IP addresses radio button. + currentIpAddressesRadioButton.isChecked = false + + // Set the background of the saved IP addresses linear layout to be transparent. + savedIpAddressesLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the current IP addresses linear layout. + currentIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Set the current IP addresses card view listener. + currentIpAddressesCardView.setOnClickListener { + // Check the current IP addresses radio button. + currentIpAddressesRadioButton.isChecked = true + + // Uncheck the saved IP addresses radio button. + savedIpAddressesRadioButton.isChecked = false + + // Set the background of the current IP addresses linear layout to be transparent. + currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the saved IP addresses linear layout. + savedIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Set the current IP addresses radio button listener. + currentIpAddressesRadioButton.setOnClickListener { + // Check the current IP addresses radio button. + currentIpAddressesRadioButton.isChecked = true + + // Uncheck the saved IP addresses radio button. + savedIpAddressesRadioButton.isChecked = false + + // Set the background of the current IP addresses linear layout to be transparent. + currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent) + + // Darken the background of the saved IP addresses linear layout. + savedIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background) + } + + // Set the scroll Y. + domainSettingsScrollView.post { domainSettingsScrollView.scrollY = scrollY } + + // Return the domain settings view. + return domainSettingsView + } + + private fun checkDomainNameAgainstCertificate(domainName: String?, certificateCommonName: String?): Boolean { + // Initialize the domain names match tracker. + var domainNamesMatch = false + + // Check various wildcard permutations if the domain name and the certificate Common Name are not empty. + if ((domainName != null) && (certificateCommonName != null)) { + // Check if the domains match. + if (domainName == certificateCommonName) + domainNamesMatch = true + + // If the domain name starts with a wildcard, check the base domain against all the subdomains of the certificate Common Name. + if (!domainNamesMatch && domainName.startsWith("*.") && domainName.length > 2) { + // Remove the initial `*.`. + val baseDomainName = domainName.substring(2) + + // Create a copy of the certificate Common Name to test subdomains. + var certificateCommonNameSubdomain: String = certificateCommonName + + // Check all the subdomains in the certificate Common Name subdomain against the base domain name. + while (!domainNamesMatch && certificateCommonNameSubdomain.contains(".")) { // Stop checking if the domain names match or if there are no more dots. + // Test the certificate Common Name subdomain against the base domain name. + if (certificateCommonNameSubdomain == baseDomainName) + domainNamesMatch = true + + // Strip out the lowest subdomain of the certificate Common Name subdomain. + certificateCommonNameSubdomain = try { + certificateCommonNameSubdomain.substring(certificateCommonNameSubdomain.indexOf(".") + 1) + } catch (e: IndexOutOfBoundsException) { // The certificate Common Name subdomain ends with a dot. + "" + } + } + } + + // If the certificate Common Name starts with a wildcard, check the base common name against all the subdomains of the domain name. + if (!domainNamesMatch && certificateCommonName.startsWith("*.") && certificateCommonName.length > 2) { + // Remove the initial `*.`. + val baseCertificateCommonName = certificateCommonName.substring(2) + + // Setup a copy of domain name to test subdomains. + var domainNameSubdomain: String = domainName + + // Check all the subdomains in the domain name subdomain against the base certificate Common Name. + while (!domainNamesMatch && domainNameSubdomain.contains(".") && domainNameSubdomain.length > 2) { + // Test the domain name subdomain against the base certificate Common Name. + if (domainNameSubdomain == baseCertificateCommonName) + domainNamesMatch = true + + // Strip out the lowest subdomain of the domain name subdomain. + domainNameSubdomain = try { + domainNameSubdomain.substring(domainNameSubdomain.indexOf(".") + 1) + } catch (e: IndexOutOfBoundsException) { // `domainNameSubdomain` ends with a dot. + "" + } + } + } + + // If both names start with a wildcard, check if the root of one contains the root of the other. + if (!domainNamesMatch && domainName.startsWith("*.") && domainName.length > 2 && certificateCommonName.startsWith("*.") && certificateCommonName.length > 2) { + // Remove the wildcards. + val rootDomainName = domainName.substring(2) + val rootCertificateCommonName = certificateCommonName.substring(2) + + // Check if one name ends with the contents of the other. If so, there will be overlap in the their wildcard subdomains. + if (rootDomainName.endsWith(rootCertificateCommonName) || rootCertificateCommonName.endsWith(rootDomainName)) + domainNamesMatch = true + } + } + + return domainNamesMatch + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java deleted file mode 100644 index 92d4b885..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright © 2017-2020,2022 Soren Stoutner . - * - * This file is part of Privacy Browser Android . - * - * Privacy Browser Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privacy Browser Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privacy Browser Android. If not, see . - */ - -package com.stoutner.privacybrowser.fragments; - -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; - -import com.google.android.material.floatingactionbutton.FloatingActionButton; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.activities.DomainsActivity; - -public class DomainsListFragment extends Fragment { - // Instantiate the dismiss snackbar interface. - private DismissSnackbarInterface dismissSnackbarInterface; - - // Define the public dismiss snackbar interface. - public interface DismissSnackbarInterface { - void dismissSnackbar(); - } - - public void onAttach(@NonNull Context context) { - // Run the default commands. - super.onAttach(context); - - // Populate the dismiss snackbar interface. - dismissSnackbarInterface = (DismissSnackbarInterface) context; - } - - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate `domains_list_fragment`. `false` does not attach it to the root `container`. - View domainsListFragmentView = inflater.inflate(R.layout.domains_list_fragment, container, false); - - // Get a handle for the domains listview. - ListView domainsListView = domainsListFragmentView.findViewById(R.id.domains_listview); - - // Remove the incorrect lint error below that `.getSupportFragmentManager()` might be null. - assert getActivity() != null; - - // Get a handle for the support fragment manager. - final FragmentManager supportFragmentManager = getActivity().getSupportFragmentManager(); - - domainsListView.setOnItemClickListener((AdapterView parent, View view, int position, long id) -> { - // Dismiss the snackbar if it is visible. - dismissSnackbarInterface.dismissSnackbar(); - - // Save the current domain settings if operating in two-paned mode and a domain is currently selected. - if (DomainsActivity.twoPanedMode && DomainsActivity.deleteMenuItem.isEnabled()) { - // Get a handle for the domain settings fragment. - Fragment domainSettingsFragment = supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container); - - // Remove the incorrect lint warning below that the domain settings fragment might be null. - assert domainSettingsFragment != null; - - // Get a handle for the domain settings fragment view. - View domainSettingsFragmentView = domainSettingsFragment.getView(); - - // Remove the incorrect lint warning below that the domain settings fragment view might be null. - assert domainSettingsFragmentView != null; - - // Get a handle for the domains activity. - DomainsActivity domainsActivity = new DomainsActivity(); - - // Save the domain settings. - domainsActivity.saveDomainSettings(domainSettingsFragmentView, getResources()); - } - - // Store the new `currentDomainDatabaseId`, converting it from `long` to `int` to match the format of the domains database. - DomainsActivity.currentDomainDatabaseId = (int) id; - - // Add `currentDomainDatabaseId` to `argumentsBundle`. - Bundle argumentsBundle = new Bundle(); - argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, DomainsActivity.currentDomainDatabaseId); - - // Add the arguments bundle to the domain settings fragment. - DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment(); - domainSettingsFragment.setArguments(argumentsBundle); - - // Check to see if the device is in two paned mode. - if (DomainsActivity.twoPanedMode) { // The device in in two-paned mode. - // enable `deleteMenuItem` if the system is not waiting for a `Snackbar` to be dismissed. - if (!DomainsActivity.dismissingSnackbar) { - // Enable the delete menu item. - DomainsActivity.deleteMenuItem.setEnabled(true); - - // Set the delete icon. - DomainsActivity.deleteMenuItem.setIcon(R.drawable.delete_enabled); - } - - // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit(); - } else { // The device in in single-paned mode - // Save the domains listview position. - DomainsActivity.domainsListViewPosition = domainsListView.getFirstVisiblePosition(); - - // Show `deleteMenuItem` if the system is not waiting for a `Snackbar` to be dismissed. - if (!DomainsActivity.dismissingSnackbar) { - DomainsActivity.deleteMenuItem.setVisible(true); - } - - // Hide the add domain FAB. - FloatingActionButton addDomainFAB = getActivity().findViewById(R.id.add_domain_fab); - addDomainFAB.hide(); - - // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit(); - } - }); - - // Return the domains list fragment. - return domainsListFragmentView; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.kt b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.kt new file mode 100644 index 00000000..558bba2c --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.kt @@ -0,0 +1,136 @@ +/* + * Copyright © 2017-2020,2022 Soren Stoutner . + * + * This file is part of Privacy Browser Android . + * + * Privacy Browser Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser Android. If not, see . + */ + +package com.stoutner.privacybrowser.fragments + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.AdapterView.OnItemClickListener +import android.widget.ListView + +import androidx.fragment.app.Fragment + +import com.google.android.material.floatingactionbutton.FloatingActionButton + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.activities.DomainsActivity + +class DomainsListFragment : Fragment() { + // Declare the class variables. + private lateinit var dismissSnackbarInterface: DismissSnackbarInterface + private lateinit var saveDomainSettingsInterface: SaveDomainSettingsInterface + + // Define the public dismiss snackbar interface. + interface DismissSnackbarInterface { + fun dismissSnackbar() + } + + // Define the public save domain interface. + interface SaveDomainSettingsInterface { + fun saveDomainSettings(view: View) + } + + override fun onAttach(context: Context) { + // Run the default commands. + super.onAttach(context) + + // Populate the interfaces. + dismissSnackbarInterface = context as DismissSnackbarInterface + saveDomainSettingsInterface = context as SaveDomainSettingsInterface + } + + override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + // Inflate the layout. The fragment will take care of attaching the root automatically. + val domainsListFragmentView = layoutInflater.inflate(R.layout.domains_list_fragment, container, false) + + // Get a handle for the domains list view. + val domainsListView = domainsListFragmentView.findViewById(R.id.domains_listview) + + // Get a handle for the support fragment manager. + val supportFragmentManager = requireActivity().supportFragmentManager + + // Handle clicks on the domains list view. + domainsListView.onItemClickListener = OnItemClickListener { _: AdapterView<*>, _: View, _: Int, id: Long -> + // Dismiss the snackbar if it is visible. + dismissSnackbarInterface.dismissSnackbar() + + // Save the current domain settings if operating in two-paned mode and a domain is currently selected. + if (DomainsActivity.twoPanedMode && DomainsActivity.deleteMenuItem.isEnabled) { + // Get a handle for the domain settings fragment. + val domainSettingsFragment = supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!! + + // Get a handle for the domain settings fragment view. + val domainSettingsFragmentView = domainSettingsFragment.requireView() + + // Save the domain settings. + saveDomainSettingsInterface.saveDomainSettings(domainSettingsFragmentView) + } + + // Store the new current domain database ID, converting it from long to int to match the format of the domains database. + DomainsActivity.currentDomainDatabaseId = id.toInt() + + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Add the current domain database ID to the arguments bundle. + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, DomainsActivity.currentDomainDatabaseId) + + // Create a new domains settings fragment instance. + val domainSettingsFragment = DomainSettingsFragment() + + // Add the arguments bundle to the domain settings fragment. + domainSettingsFragment.arguments = argumentsBundle + + // Check to see if the device is in two paned mode. + if (DomainsActivity.twoPanedMode) { // The device in in two-paned mode. + // Enable the delete menu item if the system is not waiting for a snackbar to be dismissed. + if (!DomainsActivity.dismissingSnackbar) { + // Enable the delete menu item. + DomainsActivity.deleteMenuItem.isEnabled = true + } + + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit() + } else { // The device in in single-paned mode + // Save the domains listview position. + DomainsActivity.domainsListViewPosition = domainsListView.firstVisiblePosition + + // Show the delete menu item if the system is not waiting for a snackbar to be dismissed. + if (!DomainsActivity.dismissingSnackbar) + DomainsActivity.deleteMenuItem.isVisible = true + + // Get a handle for the add domain floating action button. + val addDomainFab = requireActivity().findViewById(R.id.add_domain_fab) + + // Hide the add domain FAB. + addDomainFab.hide() + + // Display the domain settings fragment. + supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit() + } + } + + // Return the domains list fragment. + return domainsListFragmentView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideWebViewFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideWebViewFragment.java deleted file mode 100644 index 558735e5..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideWebViewFragment.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright © 2016-2022 Soren Stoutner . - * - * This file is part of Privacy Browser Android . - * - * Privacy Browser Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privacy Browser Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privacy Browser Android. If not, see . - */ - -package com.stoutner.privacybrowser.fragments; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebResourceRequest; -import android.webkit.WebResourceResponse; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.webkit.WebSettingsCompat; -import androidx.webkit.WebViewAssetLoader; -import androidx.webkit.WebViewFeature; - -import com.stoutner.privacybrowser.R; - -public class GuideWebViewFragment extends Fragment { - // Declare the class constants. - private final static String TAB_NUMBER = "tab_number"; - - // Declare the class variables. - private int tabNumber; - - // Declare the class views. - private View webViewLayout; - - // Store the tab number in the arguments bundle. - public static GuideWebViewFragment createTab (int tabNumber) { - // Create a bundle. - Bundle bundle = new Bundle(); - - // Store the tab number in the bundle. - bundle.putInt(TAB_NUMBER, tabNumber); - - // Create a new guide tab fragment. - GuideWebViewFragment guideWebViewFragment = new GuideWebViewFragment(); - - // Add the bundle to the fragment. - guideWebViewFragment.setArguments(bundle); - - // Return the new fragment. - return guideWebViewFragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - // Run the default commands. - super.onCreate(savedInstanceState); - - // Get a handle for the arguments. - Bundle arguments = getArguments(); - - // Remove the lint warning below that arguments might be null. - assert arguments != null; - - // Store the tab number in a class variable. - tabNumber = arguments.getInt(TAB_NUMBER); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout. The fragment will take care of attaching the root automatically. - webViewLayout = inflater.inflate(R.layout.bare_webview, container, false); - - // Get a handle for the tab WebView. - WebView tabWebView = (WebView) webViewLayout; - - // Get a handle for the context. - Context context = getContext(); - - // Remove the incorrect lint warning below that the context might be null. - assert context != null; - - // Create a WebView asset loader. - final WebViewAssetLoader webViewAssetLoader = new WebViewAssetLoader.Builder().addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(context)).build(); - - // Set a WebView client. - tabWebView.setWebViewClient(new WebViewClient() { - // `shouldOverrideUrlLoading` allows sending of external links back to the main Privacy Browser WebView. The deprecated `shouldOverrideUrlLoading` must be used until API >= 24. - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - // Create an intent to view the URL. - Intent urlIntent = new Intent(Intent.ACTION_VIEW); - - // Add the URL to the intent. - urlIntent.setData(Uri.parse(url)); - - // Make it so. - startActivity(urlIntent); - return true; - } - - @Override - public WebResourceResponse shouldInterceptRequest(WebView webView, WebResourceRequest webResourceRequest) { - // Have the WebView asset loader process the request. - // This allows using the `appassets.androidplatform.net` URL, which handles the loading of SVG files, which otherwise is prevented by the CORS policy. - return webViewAssetLoader.shouldInterceptRequest(webResourceRequest.getUrl()); - } - }); - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Check to see if the app is in night mode. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The app is in night mode. - // Apply the dark WebView theme. - WebSettingsCompat.setForceDark(tabWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } - - // Load the indicated tab. The tab numbers start at 0. - switch (tabNumber) { - case 0: - // Load the Overview tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_overview.html"); - break; - - case 1: - // Load the JavaScript tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_javascript.html"); - break; - - case 2: - // Load the Local Storage tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_local_storage.html"); - break; - - case 3: - // Load the User Agent tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_user_agent.html"); - break; - - case 4: - // Load the Requests tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_requests.html"); - break; - - case 5: - // Load the Domain Settings tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_domain_settings.html"); - break; - - case 6: - // Load the SSL Certificates tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_ssl_certificates.html"); - break; - - case 7: - // Load the Proxies tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_proxies.html"); - break; - - case 8: - // Load the Tracking IDs tab. - tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_tracking_ids.html"); - break; - } - - // Scroll the WebView if the saved instance state is not null. - if (savedInstanceState != null) { - tabWebView.post(() -> tabWebView.setScrollY(savedInstanceState.getInt("scroll_y"))); - } - - // Return the formatted WebView layout. - return webViewLayout; - } - - @Override - public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { - // Run the default commands. - super.onSaveInstanceState(savedInstanceState); - - // Get a handle for the tab WebView. A class variable cannot be used because it gets out of sync when restarting. - WebView tabWebView = (WebView) webViewLayout; - - // Save the scroll Y position if the tab WebView is not null, which can happen if a tab is not currently selected. - if (tabWebView != null) { - savedInstanceState.putInt("scroll_y", tabWebView.getScrollY()); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideWebViewFragment.kt b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideWebViewFragment.kt new file mode 100644 index 00000000..e572a634 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideWebViewFragment.kt @@ -0,0 +1,162 @@ +/* + * Copyright © 2016-2022 Soren Stoutner . + * + * This file is part of Privacy Browser Android . + * + * Privacy Browser Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser Android. If not, see . + */ + +package com.stoutner.privacybrowser.fragments + +import android.content.Intent +import android.content.res.Configuration +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient + +import androidx.fragment.app.Fragment +import androidx.webkit.WebSettingsCompat +import androidx.webkit.WebViewAssetLoader +import androidx.webkit.WebViewAssetLoader.AssetsPathHandler +import androidx.webkit.WebViewFeature + +import com.stoutner.privacybrowser.R + +// Define the class constants. +private const val TAB_NUMBER = "tab_number" +private const val SCROLL_Y = "scroll_y" + +class GuideWebViewFragment : Fragment() { + // Define the class variables. + private var tabNumber = 0 + + // Declare the class views. + private lateinit var webViewLayout: View + + companion object { + fun createTab(tabNumber: Int): GuideWebViewFragment { + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the tab number in the bundle. + argumentsBundle.putInt(TAB_NUMBER, tabNumber) + + // Create a new instance of the tab fragment. + val guideWebViewFragment = GuideWebViewFragment() + + // Add the arguments bundle to the fragment. + guideWebViewFragment.arguments = argumentsBundle + + // Return the new fragment. + return guideWebViewFragment + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + // Run the default commands. + super.onCreate(savedInstanceState) + + // Store the tab number in a class variable. + tabNumber = requireArguments().getInt(TAB_NUMBER) + } + + override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + // Inflate the layout. The fragment will take care of attaching the root automatically. + webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false) + + // Get a handle for the tab WebView. + val tabWebView = webViewLayout as WebView + + // Create a WebView asset loader. + val webViewAssetLoader = WebViewAssetLoader.Builder().addPathHandler("/assets/", AssetsPathHandler(requireContext())).build() + + // Set a WebView client. + tabWebView.webViewClient = object : WebViewClient() { + // Send external links back to the main Privacy Browser WebView. The deprecated `shouldOverrideUrlLoading` must be used until API >= 24. + @Deprecated("Deprecated in Java") + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + // Create an intent to view the URL. + val urlIntent = Intent(Intent.ACTION_VIEW) + + // Add the URL to the intent. + urlIntent.data = Uri.parse(url) + + // Make it so. + startActivity(urlIntent) + + // Consume the click. + return true + } + + // Process asset requests with the asset loader. + override fun shouldInterceptRequest(webView: WebView, webResourceRequest: WebResourceRequest): WebResourceResponse? { + // This allows using the `appassets.androidplatform.net` URL, which handles the loading of SVG files, which otherwise is prevented by the CORS policy. + return webViewAssetLoader.shouldInterceptRequest(webResourceRequest.url) + } + } + + // Get the current theme status. + val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + + // Check to see if the app is in night mode. This can be removed once the minimum API >= 33. + if ((Build.VERSION.SDK_INT < 33) && (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The app is in night mode. + // Apply the dark WebView theme. + @Suppress("DEPRECATION") + WebSettingsCompat.setForceDark(tabWebView.settings, WebSettingsCompat.FORCE_DARK_ON) + } + + // Load the indicated tab. The tab numbers start at 0. + when (tabNumber) { + 0 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_overview.html") + 1 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_javascript.html") + 2 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_local_storage.html") + 3 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_user_agent.html") + 4 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_requests.html") + 5 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_domain_settings.html") + 6 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_ssl_certificates.html") + 7 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_proxies.html") + 8 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/guide_tracking_ids.html") + } + + // Scroll the WebView if the saved instance state is not null. + if (savedInstanceState != null) { + tabWebView.post { + tabWebView.scrollY = savedInstanceState.getInt(SCROLL_Y) + } + } + + // Return the formatted WebView layout. + return webViewLayout + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + // Run the default commands. + super.onSaveInstanceState(savedInstanceState) + + // Get a handle for the tab WebView. A class variable cannot be used because it gets out of sync when restarting. + val tabWebView = webViewLayout as WebView? + + // Save the scroll Y position if the tab WebView is not null, which can happen if a tab is not currently selected. + if (tabWebView != null) { + savedInstanceState.putInt(SCROLL_Y, tabWebView.scrollY) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java index a964a9ca..d7aecd30 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java @@ -127,7 +127,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { cookiesPreference = findPreference(getString(R.string.cookies_key)); domStoragePreference = findPreference("dom_storage"); formDataPreference = findPreference("save_form_data"); // The form data preference can be removed once the minimum API >= 26. - userAgentPreference = findPreference("user_agent"); + userAgentPreference = findPreference(getString(R.string.user_agent_key)); customUserAgentPreference = findPreference("custom_user_agent"); xRequestedWithHeaderPreference = findPreference(getString(R.string.x_requested_with_header_key)); incognitoModePreference = findPreference("incognito_mode"); @@ -154,17 +154,17 @@ public class SettingsFragment extends PreferenceFragmentCompat { clearLogcatPreference = findPreference(getString(R.string.clear_logcat_key)); clearCachePreference = findPreference("clear_cache"); homepagePreference = findPreference("homepage"); - fontSizePreference = findPreference("font_size"); + fontSizePreference = findPreference(getString(R.string.font_size_key)); openIntentsInNewTabPreference = findPreference("open_intents_in_new_tab"); - swipeToRefreshPreference = findPreference("swipe_to_refresh"); + swipeToRefreshPreference = findPreference(getString(R.string.swipe_to_refresh_key)); downloadWithExternalAppPreference = findPreference(getString(R.string.download_with_external_app_key)); scrollAppBarPreference = findPreference(getString(R.string.scroll_app_bar_key)); bottomAppBarPreference = findPreference(getString(R.string.bottom_app_bar_key)); displayAdditionalAppBarIconsPreference = findPreference(getString(R.string.display_additional_app_bar_icons_key)); appThemePreference = findPreference("app_theme"); - webViewThemePreference = findPreference("webview_theme"); - wideViewportPreference = findPreference("wide_viewport"); - displayWebpageImagesPreference = findPreference("display_webpage_images"); + webViewThemePreference = findPreference(getString(R.string.webview_theme_key)); + wideViewportPreference = findPreference(getString(R.string.wide_viewport_key)); + displayWebpageImagesPreference = findPreference(getString(R.string.display_webpage_images_key)); // Remove the lint warnings below that the preferences might be null. assert javaScriptPreference != null; @@ -215,7 +215,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { domStoragePreference.setDependency("javascript"); // Get strings from the preferences. - String userAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value)); + String userAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value)); String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value)); String proxyString = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value)); @@ -288,7 +288,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { } // Set the summary text for the custom user agent preference. - customUserAgentPreference.setSummary(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); + customUserAgentPreference.setSummary(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))); // Only enable the custom user agent preference if the user agent is set to `Custom`. customUserAgentPreference.setEnabled(Objects.equals(userAgentPreference.getSummary(), getString(R.string.custom_user_agent))); @@ -348,7 +348,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { // Set the font size as the summary text for the preference. - fontSizePreference.setSummary(sharedPreferences.getString("font_size", getString(R.string.font_size_default_value)) + "%"); + fontSizePreference.setSummary(sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value)) + "%"); // Get the app theme string arrays. @@ -385,7 +385,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values); // Get the current WebView theme. - String currentWebViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value)); + String currentWebViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value)); // Define a WebView theme entry number. int webViewThemeEntryNumber; @@ -624,7 +624,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { } // Set the swipe to refresh preference icon. - if (sharedPreferences.getBoolean("swipe_to_refresh", true)) + if (sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true)) swipeToRefreshPreference.setIcon(R.drawable.refresh_enabled); else swipeToRefreshPreference.setIcon(R.drawable.refresh_disabled); @@ -682,14 +682,14 @@ public class SettingsFragment extends PreferenceFragmentCompat { } // Set the wide viewport preference icon. - if (sharedPreferences.getBoolean("wide_viewport", true)) { + if (sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true)) { wideViewportPreference.setIcon(R.drawable.wide_viewport_enabled); } else { wideViewportPreference.setIcon(R.drawable.wide_viewport_disabled); } // Set the display webpage images preference icon. - if (sharedPreferences.getBoolean("display_webpage_images", true)) { + if (sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true)) { displayWebpageImagesPreference.setIcon(R.drawable.images_enabled); } else { displayWebpageImagesPreference.setIcon(R.drawable.images_disabled); @@ -794,7 +794,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { case "user_agent": // Get the new user agent name. - String newUserAgentName = sharedPreferences.getString("user_agent", context.getString(R.string.user_agent_default_value)); + String newUserAgentName = sharedPreferences.getString(context.getString(R.string.user_agent_key), context.getString(R.string.user_agent_default_value)); // Get the array position for the new user agent name. int newUserAgentArrayPosition = userAgentNamesArray.getPosition(newUserAgentName); @@ -840,7 +840,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { case "custom_user_agent": // Set the new custom user agent as the summary text for the preference. - customUserAgentPreference.setSummary(sharedPreferences.getString("custom_user_agent", context.getString(R.string.custom_user_agent_default_value))); + customUserAgentPreference.setSummary(sharedPreferences.getString(context.getString(R.string.custom_user_agent_key), context.getString(R.string.custom_user_agent_default_value))); break; case "x_requested_with_header": @@ -1189,7 +1189,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { case "font_size": // Update the font size summary text. - fontSizePreference.setSummary(sharedPreferences.getString("font_size", context.getString(R.string.font_size_default_value)) + "%"); + fontSizePreference.setSummary(sharedPreferences.getString(context.getString(R.string.font_size_key), context.getString(R.string.font_size_default_value)) + "%"); break; case "open_intents_in_new_tab": @@ -1202,7 +1202,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { case "swipe_to_refresh": // Update the icon. - if (sharedPreferences.getBoolean("swipe_to_refresh", true)) + if (sharedPreferences.getBoolean(context.getString(R.string.swipe_to_refresh_key), true)) swipeToRefreshPreference.setIcon(R.drawable.refresh_enabled); else swipeToRefreshPreference.setIcon(R.drawable.refresh_disabled); @@ -1300,7 +1300,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { webViewThemePreference.setEnabled((Build.VERSION.SDK_INT < 33) || (appThemeEntryNumber != 1)); // Get the WebView theme. - String webViewTheme = sharedPreferences.getString("webview_theme", context.getString(R.string.webview_theme_default_value)); + String webViewTheme = sharedPreferences.getString(context.getString(R.string.webview_theme_key), context.getString(R.string.webview_theme_default_value)); // Declare a WebView theme entry number. int webViewThemeEntryNumber; @@ -1348,7 +1348,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { case "webview_theme": // Get the new WebView theme. - String newWebViewTheme = sharedPreferences.getString("webview_theme", context.getString(R.string.webview_theme_default_value)); + String newWebViewTheme = sharedPreferences.getString(context.getString(R.string.webview_theme_key), context.getString(R.string.webview_theme_default_value)); // Declare a new WebView theme entry number. int newWebViewThemeEntryNumber; @@ -1396,7 +1396,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { case "wide_viewport": // Update the icon. - if (sharedPreferences.getBoolean("wide_viewport", true)) { + if (sharedPreferences.getBoolean(context.getString(R.string.wide_viewport_key), true)) { wideViewportPreference.setIcon(R.drawable.wide_viewport_enabled); } else { wideViewportPreference.setIcon(R.drawable.wide_viewport_disabled); @@ -1405,7 +1405,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { case "display_webpage_images": // Update the icon. - if (sharedPreferences.getBoolean("display_webpage_images", true)) { + if (sharedPreferences.getBoolean(context.getString(R.string.display_webpage_images_key), true)) { displayWebpageImagesPreference.setIcon(R.drawable.images_enabled); } else { displayWebpageImagesPreference.setIcon(R.drawable.images_disabled); diff --git a/app/src/main/res/color/black_icon_selector.xml b/app/src/main/res/color/black_icon_selector.xml new file mode 100644 index 00000000..43411004 --- /dev/null +++ b/app/src/main/res/color/black_icon_selector.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/delete_disabled.xml b/app/src/main/res/drawable/delete.xml similarity index 89% rename from app/src/main/res/drawable/delete_disabled.xml rename to app/src/main/res/drawable/delete.xml index 5309f391..57343e94 100644 --- a/app/src/main/res/drawable/delete_disabled.xml +++ b/app/src/main/res/drawable/delete.xml @@ -8,6 +8,6 @@ android:viewportWidth="24" > \ No newline at end of file diff --git a/app/src/main/res/drawable/delete_enabled.xml b/app/src/main/res/drawable/delete_enabled.xml deleted file mode 100644 index 323569d7..00000000 --- a/app/src/main/res/drawable/delete_enabled.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/bookmarks_context_menu.xml b/app/src/main/res/menu/bookmarks_context_menu.xml index cad87cbc..2a4c2e08 100644 --- a/app/src/main/res/menu/bookmarks_context_menu.xml +++ b/app/src/main/res/menu/bookmarks_context_menu.xml @@ -54,7 +54,7 @@ android:id="@+id/delete_bookmark" android:title="@string/delete" android:orderInCategory="50" - android:icon="@drawable/delete_enabled" + android:icon="@drawable/delete" app:showAsAction="ifRoom" /> \ No newline at end of file diff --git a/app/src/main/res/menu/domains_options_menu.xml b/app/src/main/res/menu/domains_options_menu.xml index 03429d57..23d2dbb2 100644 --- a/app/src/main/res/menu/domains_options_menu.xml +++ b/app/src/main/res/menu/domains_options_menu.xml @@ -27,6 +27,6 @@ android:id="@+id/delete_domain" android:title="@string/delete" android:orderInCategory="10" - android:icon="@drawable/delete_enabled" + android:icon="@drawable/delete" app:showAsAction="ifRoom" /> \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 3fede875..e34c4998 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -34,4 +34,5 @@ @color/dark_blue_40 @color/red_900 @color/gray_700 + @color/black_translucent_33 \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 4cd47c04..ae185134 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -34,6 +34,7 @@ @color/green_200 @color/red_a700 @color/gray_300 + @color/black_translucent_11 #66000000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb9da095..676f7554 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -663,11 +663,18 @@ bottom_app_bar cookies clear_logcat - download_with_external_app + custom_user_agent display_additional_app_bar_icons + display_webpage_images + download_with_external_app + font_size proxy_custom_url scroll_app_bar + swipe_to_refresh tracking_queries + user_agent + webview_theme + wide_viewport x_requested_with_header diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 14396430..4957727c 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -51,7 +51,7 @@ android:defaultValue="false" /> @@ -257,7 +257,7 @@ @@ -308,20 +308,20 @@ android:icon="@drawable/app_theme" /> diff --git a/build.gradle b/build.gradle index 754dd4c1..4c4ece74 100644 --- a/build.gradle +++ b/build.gradle @@ -26,8 +26,8 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10" + classpath 'com.android.tools.build:gradle:7.3.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8b7f5fed..956d64de 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip -- 2.45.2