]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Convert a number of files to Kotlin. https://redmine.stoutner.com/issues/641
authorSoren Stoutner <soren@stoutner.com>
Wed, 20 Jan 2021 01:07:22 +0000 (18:07 -0700)
committerSoren Stoutner <soren@stoutner.com>
Wed, 20 Jan 2021 01:07:22 +0000 (18:07 -0700)
33 files changed:
app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/adapters/PinnedMismatchPagerAdapter.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/AboutViewSourceDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/FontSizeDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt [new file with mode: 0644]
app/src/main/res/layout/pinned_mismatch_scrollview.xml [deleted file]
app/src/main/res/layout/pinned_mismatch_tab_linearlayout.xml [new file with mode: 0644]
app/src/standard/java/com/stoutner/privacybrowser/helpers/AdHelper.java
build.gradle

index 0ff3f8250ce3e64f6e112384c72aac459203cb8b..7d0228fb5a76ded69af525043f8f4d33796da62a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -101,6 +101,9 @@ public class AboutActivity extends AppCompatActivity implements SaveDialog.SaveL
         // Store the blocklist versions.
         String[] blocklistVersions = launchingIntent.getStringArrayExtra("blocklist_versions");
 
+        // Remove the incorrect lint warning below that the blocklist versions might be null.
+        assert blocklistVersions != null;
+
         // Set the content view.
         setContentView(R.layout.about_coordinatorlayout);
 
index 689773cf715aba0d312cd940bd5c26122f172ca4..cfceab0d79948726d84318b718a3a7e1f1814375 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -71,15 +71,9 @@ import java.util.ArrayList;
 public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
         EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
 
-    // `currentFolder` is public static so it can be accessed from `MoveToFolderDialog` and `BookmarksDatabaseViewActivity`.
-    // It is used in `onCreate`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
-    // and `loadFolder()`.
+    // `currentFolder` is public static so it can be accessed from `BookmarksDatabaseViewActivity`.
     public static String currentFolder;
 
-    // `checkedItemIds` is public static so it can be accessed from `MoveToFolderDialog`.  It is also used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
-    // and `updateMoveIcons()`.
-    public static long[] checkedItemIds;
-
     // `restartFromBookmarksDatabaseViewActivity` is public static so it can be accessed from `BookmarksDatabaseViewActivity`.  It is also used in `onRestart()`.
     public static boolean restartFromBookmarksDatabaseViewActivity;
 
@@ -420,11 +414,8 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                     // Update the enabled status of the move icons.
                     updateMoveIcons();
                 } else if (menuItemId == R.id.move_to_folder) {  // Move to folder.
-                    // Store the checked item IDs for use by the alert dialog.
-                    checkedItemIds = bookmarksListView.getCheckedItemIds();
-
                     // Instantiate the move to folder alert dialog.
-                    DialogFragment moveToFolderDialog = new MoveToFolderDialog();
+                    DialogFragment moveToFolderDialog = MoveToFolderDialog.moveBookmarks(currentFolder, bookmarksListView.getCheckedItemIds());
 
                     // Show the move to folder alert dialog.
                     moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
@@ -870,7 +861,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
     }
 
     @Override
-    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
+    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
         // Get the dialog from the dialog fragment.
         Dialog dialog = dialogFragment.getDialog();
 
index 5b39fdcba08cabd6fd0f3e9d2c1d4d07761fb4cc..e5d1f8c89507994351bc769043accd1f2f53190a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2015-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
@@ -649,11 +649,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         updatePrivacyIcons(true);
     }
 
-    // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
+    // `onStart()` runs after `onCreate()` or `onRestart()`.  This is used instead of `onResume()` so the commands aren't called every time the screen is partially hidden.
     @Override
-    public void onResume() {
+    public void onStart() {
         // Run the default commands.
-        super.onResume();
+        super.onStart();
 
         // Resume any WebViews.
         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
@@ -702,10 +702,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
+    // `onStop()` runs after `onPause()`.  It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
     @Override
-    public void onPause() {
+    public void onStop() {
         // Run the default commands.
-        super.onPause();
+        super.onStop();
 
         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
             // Get the WebView tab fragment.
@@ -2594,7 +2595,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
-    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
+    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
         // Remove the incorrect lint warning below that the dialog fragment might be null.
         assert dialogFragment != null;
 
diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.java b/app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.java
deleted file mode 100644 (file)
index 27f9729..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-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.AboutVersionFragment;
-import com.stoutner.privacybrowser.fragments.AboutWebViewFragment;
-
-import java.util.LinkedList;
-
-public class AboutPagerAdapter extends FragmentPagerAdapter {
-    // Define the class variables.
-    private final Context context;
-    private final String[] blocklistVersions;
-    private final LinkedList<Fragment> aboutFragmentList = new LinkedList<>();
-
-    public AboutPagerAdapter(FragmentManager fragmentManager, Context context, String[] blocklistVersions) {
-        // Run the default commands.
-        super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
-
-        // Store the class variables.
-        this.context = context;
-        this.blocklistVersions = blocklistVersions;
-    }
-
-    @Override
-    // Get the count of the number of tabs.
-    public int getCount() {
-        return 7;
-    }
-
-    @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.version);
-
-            case 1:
-                return context.getString(R.string.permissions);
-
-            case 2:
-                return context.getString(R.string.privacy_policy);
-
-            case 3:
-                return context.getString(R.string.changelog);
-
-            case 4:
-                return context.getString(R.string.licenses);
-
-            case 5:
-                return context.getString(R.string.contributors);
-
-            case 6:
-                return context.getString(R.string.links);
-
-            default:
-                return "";
-        }
-    }
-
-    @Override
-    @NonNull
-    // Setup each tab.
-    public Fragment getItem(int tabNumber) {
-        // Create the tab fragment and add it to the list.
-        if (tabNumber == 0){
-            // Add the version tab to the list.
-            aboutFragmentList.add(AboutVersionFragment.createTab(blocklistVersions));
-        } else {
-            // Add the WebView tab to the list.
-            aboutFragmentList.add(AboutWebViewFragment.createTab(tabNumber));
-        }
-
-        // Return the tab number fragment.
-        return aboutFragmentList.get(tabNumber);
-    }
-
-    public Fragment getTabFragment(int tabNumber) {
-        // Return the tab fragment.
-        return aboutFragmentList.get(tabNumber);
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.kt b/app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.kt
new file mode 100644 (file)
index 0000000..5196750
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+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.AboutVersionFragment
+import com.stoutner.privacybrowser.fragments.AboutWebViewFragment
+
+import java.util.LinkedList
+
+class AboutPagerAdapter(fragmentManager: FragmentManager, private val context: Context, private val blocklistVersions: Array<String>) :
+        FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+    // Define the class variables.
+    private val aboutFragmentList = LinkedList<Fragment>()
+
+    // Get the number of tabs.
+    override fun getCount(): Int {
+        // There are seven tabs.
+        return 7
+    }
+
+    // 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.version)
+            1 -> context.getString(R.string.permissions)
+            2 -> context.getString(R.string.privacy_policy)
+            3 -> context.getString(R.string.changelog)
+            4 -> context.getString(R.string.licenses)
+            5 -> context.getString(R.string.contributors)
+            6 -> context.getString(R.string.links)
+            else -> ""
+        }
+    }
+
+    // Setup each tab.
+    override fun getItem(tabNumber: Int): Fragment {
+        // Create the tab fragment and add it to the list.
+        if (tabNumber == 0) {
+            // Add the version tab to the list.
+            aboutFragmentList.add(AboutVersionFragment.createTab(blocklistVersions))
+        } else {
+            // Add the WebView tab to the list.
+            aboutFragmentList.add(AboutWebViewFragment.createTab(tabNumber))
+        }
+
+        // Return the tab fragment.
+        return aboutFragmentList[tabNumber]
+    }
+
+    // Get a tab.
+    fun getTabFragment(tabNumber: Int): Fragment {
+        // Return the tab fragment.
+        return aboutFragmentList[tabNumber]
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/PinnedMismatchPagerAdapter.kt b/app/src/main/java/com/stoutner/privacybrowser/adapters/PinnedMismatchPagerAdapter.kt
new file mode 100644 (file)
index 0000000..6156533
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * Copyright © 2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.adapters
+
+import android.content.Context
+import android.content.res.Configuration
+import android.net.Uri
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.ForegroundColorSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+
+import androidx.viewpager.widget.PagerAdapter
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.activities.MainWebViewActivity
+import com.stoutner.privacybrowser.views.NestedScrollWebView
+
+import java.text.DateFormat
+import java.util.Date
+
+// This adapter uses a PagerAdapter instead of a FragmentPagerAdapter because dialogs fragments really don't like having a nested FragmentPagerAdapter inside of them.
+class PinnedMismatchPagerAdapter(private val context: Context, private val layoutInflater: LayoutInflater, private val webViewFragmentId: Long) : PagerAdapter() {
+    override fun isViewFromObject(view: View, `object`: Any): Boolean {
+        // Check to see if the view and the object are the same.
+        return view === `object`
+    }
+
+    // Get the number of tabs.
+    override fun getCount(): Int {
+        // There are two tabs.
+        return 2
+    }
+
+    // Get the name of each tab.  Tab numbers start at 0.
+    override fun getPageTitle(tabNumber: Int): CharSequence {
+        return when (tabNumber) {
+            0 -> context.getString(R.string.current)
+            1 -> context.getString(R.string.pinned)
+            else -> ""
+        }
+    }
+
+    // Setup each tab.
+    override fun instantiateItem(container: ViewGroup, tabNumber: Int): Any {
+        // Get the current position of this WebView fragment.
+        val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
+
+        // Get the WebView tab fragment.
+        val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
+
+        // Get the WebView fragment view.
+        val webViewFragmentView = webViewTabFragment.requireView()
+
+        // Get a handle for the current WebView.
+        val nestedScrollWebView = webViewFragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)!!
+
+        // Inflate the scroll view for this tab.
+        val tabLayout = layoutInflater.inflate(R.layout.pinned_mismatch_tab_linearlayout, container, false) as ViewGroup
+
+        // Get handles for the views.
+        val domainNameTextView = tabLayout.findViewById<TextView>(R.id.domain_name)
+        val ipAddressesTextView = tabLayout.findViewById<TextView>(R.id.ip_addresses)
+        val issuedToCNameTextView = tabLayout.findViewById<TextView>(R.id.issued_to_cname)
+        val issuedToONameTextView = tabLayout.findViewById<TextView>(R.id.issued_to_oname)
+        val issuedToUNameTextView = tabLayout.findViewById<TextView>(R.id.issued_to_uname)
+        val issuedByCNameTextView = tabLayout.findViewById<TextView>(R.id.issued_by_cname)
+        val issuedByONameTextView = tabLayout.findViewById<TextView>(R.id.issued_by_oname)
+        val issuedByUNameTextView = tabLayout.findViewById<TextView>(R.id.issued_by_uname)
+        val startDateTextView = tabLayout.findViewById<TextView>(R.id.start_date)
+        val endDateTextView = tabLayout.findViewById<TextView>(R.id.end_date)
+
+        // Setup the labels.
+        val domainNameLabel = context.getString(R.string.domain_label) + "  "
+        val ipAddressesLabel = context.getString(R.string.ip_addresses) + "  "
+        val cNameLabel = context.getString(R.string.common_name) + "  "
+        val oNameLabel = context.getString(R.string.organization) + "  "
+        val uNameLabel = context.getString(R.string.organizational_unit) + "  "
+        val startDateLabel = context.getString(R.string.start_date) + "  "
+        val endDateLabel = context.getString(R.string.end_date) + "  "
+
+        // Convert the URL to a URI.
+        val currentUri = Uri.parse(nestedScrollWebView.url)
+
+        // Get the current host from the URI.
+        val domainName = currentUri.host
+
+        // Get the current website SSL certificate.
+        val sslCertificate = nestedScrollWebView.certificate
+
+        // Initialize the SSL certificate variables.
+        var currentSslIssuedToCName = ""
+        var currentSslIssuedToOName = ""
+        var currentSslIssuedToUName = ""
+        var currentSslIssuedByCName = ""
+        var currentSslIssuedByOName = ""
+        var currentSslIssuedByUName = ""
+        var currentSslStartDate: Date? = null
+        var currentSslEndDate: Date? = null
+
+        // Extract the individual pieces of information from the current website SSL certificate if it is not null.
+        if (sslCertificate != null) {
+            currentSslIssuedToCName = sslCertificate.issuedTo.cName
+            currentSslIssuedToOName = sslCertificate.issuedTo.oName
+            currentSslIssuedToUName = sslCertificate.issuedTo.uName
+            currentSslIssuedByCName = sslCertificate.issuedBy.cName
+            currentSslIssuedByOName = sslCertificate.issuedBy.oName
+            currentSslIssuedByUName = sslCertificate.issuedBy.uName
+            currentSslStartDate = sslCertificate.validNotBeforeDate
+            currentSslEndDate = sslCertificate.validNotAfterDate
+        }
+
+        // Get the pinned SSL certificate.
+        val pinnedSslCertificateArrayList = nestedScrollWebView.pinnedSslCertificate
+
+        // Extract the arrays from the array list.
+        val pinnedSslCertificateStringArray = pinnedSslCertificateArrayList[0] as Array<*>
+        val pinnedSslCertificateDateArray = pinnedSslCertificateArrayList[1] as Array<*>
+
+        // Setup the domain name spannable string builder.
+        val domainNameStringBuilder = SpannableStringBuilder(domainNameLabel + domainName)
+
+        // Initialize the spannable string builders.
+        val ipAddressesStringBuilder: SpannableStringBuilder
+        val issuedToCNameStringBuilder: SpannableStringBuilder
+        val issuedToONameStringBuilder: SpannableStringBuilder
+        val issuedToUNameStringBuilder: SpannableStringBuilder
+        val issuedByCNameStringBuilder: SpannableStringBuilder
+        val issuedByONameStringBuilder: SpannableStringBuilder
+        val issuedByUNameStringBuilder: SpannableStringBuilder
+        val startDateStringBuilder: SpannableStringBuilder
+        val endDateStringBuilder: SpannableStringBuilder
+
+        // Setup the spannable string builders for each tab.
+        if (tabNumber == 0) {  // Setup the current settings tab.
+            // Create the string builders.
+            ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.currentIpAddresses)
+            issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + currentSslIssuedToCName)
+            issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + currentSslIssuedToOName)
+            issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + currentSslIssuedToUName)
+            issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + currentSslIssuedByCName)
+            issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + currentSslIssuedByOName)
+            issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + currentSslIssuedByUName)
+
+            // Set the dates if they aren't null.  Formatting a null date causes a crash.
+            startDateStringBuilder = if (currentSslStartDate == null) {
+                SpannableStringBuilder(startDateLabel)
+            } else {
+                SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate))
+            }
+
+            endDateStringBuilder = if (currentSslEndDate == null) {
+                SpannableStringBuilder(endDateLabel)
+            } else {
+                SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate))
+            }
+        } else {  // Setup the pinned settings tab.
+            // Create the string builders.
+            ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.pinnedIpAddresses)
+            issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + pinnedSslCertificateStringArray[0])
+            issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + pinnedSslCertificateStringArray[1])
+            issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + pinnedSslCertificateStringArray[2])
+            issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + pinnedSslCertificateStringArray[3])
+            issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + pinnedSslCertificateStringArray[4])
+            issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + pinnedSslCertificateStringArray[5])
+
+            // Set the dates if they aren't null.  Formatting a null date causes a crash.
+            startDateStringBuilder = if (pinnedSslCertificateDateArray[0] == null) {
+                SpannableStringBuilder(startDateLabel)
+            } else {
+                SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(pinnedSslCertificateDateArray[0]))
+            }
+
+            endDateStringBuilder = if (pinnedSslCertificateDateArray[1] == null) {
+                SpannableStringBuilder(endDateLabel)
+            } else {
+                SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(pinnedSslCertificateDateArray[1]))
+            }
+        }
+
+        // Define the color spans.
+        val blueColorSpan: ForegroundColorSpan
+        val redColorSpan: ForegroundColorSpan
+
+        // Get the current theme status.
+        val currentThemeStatus = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+        // Set the color spans according to the theme.  The deprecated `resources` must be used until the minimum API >= 23.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            @Suppress("DEPRECATION")
+            blueColorSpan = ForegroundColorSpan(context.resources.getColor(R.color.blue_700))
+            @Suppress("DEPRECATION")
+            redColorSpan = ForegroundColorSpan(context.resources.getColor(R.color.red_a700))
+        } else {
+            @Suppress("DEPRECATION")
+            blueColorSpan = ForegroundColorSpan(context.resources.getColor(R.color.violet_700))
+            @Suppress("DEPRECATION")
+            redColorSpan = ForegroundColorSpan(context.resources.getColor(R.color.red_900))
+        }
+
+        // Set the domain name to be blue.
+        domainNameStringBuilder.setSpan(blueColorSpan, domainNameLabel.length, domainNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+
+        // Color coordinate the IP addresses if they are pinned.
+        if (nestedScrollWebView.hasPinnedIpAddresses()) {
+            if (nestedScrollWebView.currentIpAddresses == nestedScrollWebView.pinnedIpAddresses) {
+                ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                ipAddressesStringBuilder.setSpan(redColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+        }
+
+        // Color coordinate the SSL certificate fields if they are pinned.
+        if (nestedScrollWebView.hasPinnedSslCertificate()) {
+            if (currentSslIssuedToCName == pinnedSslCertificateStringArray[0]) {
+                issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+
+            if (currentSslIssuedToOName == pinnedSslCertificateStringArray[1]) {
+                issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+
+            if (currentSslIssuedToUName == pinnedSslCertificateStringArray[2]) {
+                issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+
+            if (currentSslIssuedByCName == pinnedSslCertificateStringArray[3]) {
+                issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+
+            if (currentSslIssuedByOName == pinnedSslCertificateStringArray[4]) {
+                issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+
+            if (currentSslIssuedByUName == pinnedSslCertificateStringArray[5]) {
+                issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+
+            if (currentSslStartDate != null && currentSslStartDate == pinnedSslCertificateDateArray[0]) {
+                startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+
+            if (currentSslEndDate != null && currentSslEndDate == pinnedSslCertificateDateArray[1]) {
+                endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            } else {
+                endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+            }
+        }
+
+        // Display the strings.
+        domainNameTextView.text = domainNameStringBuilder
+        ipAddressesTextView.text = ipAddressesStringBuilder
+        issuedToCNameTextView.text = issuedToCNameStringBuilder
+        issuedToONameTextView.text = issuedToONameStringBuilder
+        issuedToUNameTextView.text = issuedToUNameStringBuilder
+        issuedByCNameTextView.text = issuedByCNameStringBuilder
+        issuedByONameTextView.text = issuedByONameStringBuilder
+        issuedByUNameTextView.text = issuedByUNameStringBuilder
+        startDateTextView.text = startDateStringBuilder
+        endDateTextView.text = endDateStringBuilder
+
+        // Add the tab layout to the container.  This needs to be manually done for pager adapters.
+        container.addView(tabLayout)
+
+        // Return the tab layout.
+        return tabLayout
+    }
+}
\ No newline at end of file
index 1b9161eb2068447a14329a97c38b7477f94b9f29..5089fa22ca8104c121db09d5405f133b80a6aeaf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2018-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -29,7 +29,7 @@ import androidx.preference.PreferenceManager
 
 import com.stoutner.privacybrowser.R
 
-class AboutViewSourceDialog: DialogFragment() {
+class AboutViewSourceDialog : DialogFragment() {
     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
         // Use a builder to create the alert dialog.
         val dialogBuilder: AlertDialog.Builder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
index 9860a04a10754309377a26eb2721bfbaf866c644..464a24ab068d5c428c4c9c42b51dbf8a9ea7fa0e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -43,15 +43,15 @@ import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
 // Declare the class constants.
 private const val URL_STRING = "url_string"
 
-class AddDomainDialog: DialogFragment() {
+class AddDomainDialog : DialogFragment() {
+    // Declare the class variables
+    private lateinit var addDomainListener: AddDomainListener
+
     // The public interface is used to send information back to the parent activity.
     interface AddDomainListener {
         fun onAddDomain(dialogFragment: DialogFragment)
     }
 
-    // Declare the class variables
-    private lateinit var addDomainListener: AddDomainListener
-
     override fun onAttach(context: Context) {
         // Run the default commands.
         super.onAttach(context)
@@ -61,7 +61,7 @@ class AddDomainDialog: DialogFragment() {
     }
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun addDomain(urlString: String): AddDomainDialog {
             // Create an arguments bundle.
@@ -102,10 +102,10 @@ class AddDomainDialog: DialogFragment() {
         // Set the view.  The parent view is `null` because it will be assigned by the alert dialog.
         dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.add_domain_dialog, null))
 
-        // Set a listener on the cancel button.  Using `null` as the listener closes the dialog without doing anything else.
+        // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
         dialogBuilder.setNegativeButton(R.string.cancel, null)
 
-        // Set a listener on the add button.
+        // Set the add button listener.
         dialogBuilder.setPositiveButton(R.string.add) { _: DialogInterface, _: Int ->
             // Return the dialog fragment to the parent activity on add.
             addDomainListener.onAddDomain(this)
index c3871863aae7a3d2ff692e1bdc693487d6565f09..0283213752ebaba68ace3cb302d678a8719de1c8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -46,15 +46,15 @@ private const val URL_STRING = "url_string"
 private const val TITLE = "title"
 private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
 
-class CreateBookmarkDialog: DialogFragment() {
+class CreateBookmarkDialog : DialogFragment() {
+    // Declare the class variables
+    private lateinit var createBookmarkListener: CreateBookmarkListener
+
     // The public interface is used to send information back to the parent activity.
     interface CreateBookmarkListener {
         fun onCreateBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap)
     }
 
-    // Declare the class variables
-    private lateinit var createBookmarkListener: CreateBookmarkListener
-
     override fun onAttach(context: Context) {
         // Run the default commands.
         super.onAttach(context)
@@ -64,7 +64,7 @@ class CreateBookmarkDialog: DialogFragment() {
     }
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun createBookmark(urlString: String, title: String, favoriteIconBitmap: Bitmap): CreateBookmarkDialog {
             // Create a favorite icon byte array output stream.
index 705466493c423e1efb238223b6f0c51687f194d8..d5fd6d82d8390996022a5183d03483fa01efc934 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -46,15 +46,15 @@ import java.io.ByteArrayOutputStream
 // Declare the class constants.
 private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
 
-class CreateBookmarkFolderDialog: DialogFragment() {
+class CreateBookmarkFolderDialog : DialogFragment() {
+    // Declare the class variables.
+    private lateinit var createBookmarkFolderListener: CreateBookmarkFolderListener
+
     // The public interface is used to send information back to the parent activity.
     interface CreateBookmarkFolderListener {
         fun onCreateBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap)
     }
 
-    // Declare the class variables.
-    private lateinit var createBookmarkFolderListener: CreateBookmarkFolderListener
-
     override fun onAttach(context: Context) {
         // Run the default commands.
         super.onAttach(context)
@@ -64,7 +64,7 @@ class CreateBookmarkFolderDialog: DialogFragment() {
     }
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun createBookmarkFolder(favoriteIconBitmap: Bitmap): CreateBookmarkFolderDialog {
             // Create a favorite icon byte array output stream.
index 21e40d47f0a1a19311d87fbf55b056306f2bb69d..b0df47fa174c009b7b745835051d0ededaf83976 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2015-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -55,14 +55,14 @@ private const val SHORTCUT_NAME = "shortcut_name"
 private const val URL_STRING = "url_string"
 private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
 
-class CreateHomeScreenShortcutDialog: DialogFragment() {
+class CreateHomeScreenShortcutDialog : DialogFragment() {
     // Declare the class views.
     private lateinit var shortcutNameEditText: EditText
     private lateinit var urlEditText: EditText
     private lateinit var openWithPrivacyBrowserRadioButton: RadioButton
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun createDialog(shortcutName: String, urlString: String, favoriteIconBitmap: Bitmap): CreateHomeScreenShortcutDialog {
             // Create a favorite icon byte array output stream.
index eb445a1615f2a4c9e3bc8ec289e1ef0aea3a59cd..c0ace69f98de2254842c3de0906090d6785ddef8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -60,12 +60,7 @@ import java.io.ByteArrayOutputStream
 private const val DATABASE_ID = "database_id"
 private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
 
-class EditBookmarkDatabaseViewDialog: DialogFragment() {
-    // The public interface is used to send information back to the parent activity.
-    interface EditBookmarkDatabaseViewListener {
-        fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap)
-    }
-
+class EditBookmarkDatabaseViewDialog : DialogFragment() {
     // Declare the class variables.
     private lateinit var editBookmarkDatabaseViewListener: EditBookmarkDatabaseViewListener
 
@@ -77,6 +72,11 @@ class EditBookmarkDatabaseViewDialog: DialogFragment() {
     private lateinit var displayOrderEditText: EditText
     private lateinit var saveButton: Button
 
+    // The public interface is used to send information back to the parent activity.
+    interface EditBookmarkDatabaseViewListener {
+        fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap)
+    }
+
     override fun onAttach(context: Context) {
         // Run the default commands.
         super.onAttach(context)
@@ -86,7 +86,7 @@ class EditBookmarkDatabaseViewDialog: DialogFragment() {
     }
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun bookmarkDatabaseId(databaseId: Int, favoriteIconBitmap: Bitmap): EditBookmarkDatabaseViewDialog {
             // Create a favorite icon byte array output stream.
index 356de47bd2666b2335eefff53dee3bff9bd24d5b..84308bf09a70d7ef30b27d9ed5a68496cfbb7374 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -50,12 +50,7 @@ import java.io.ByteArrayOutputStream
 private const val DATABASE_ID = "database_id"
 private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
 
-class EditBookmarkDialog: DialogFragment() {
-    // The public interface is used to send information back to the parent activity.
-    interface EditBookmarkListener {
-        fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap)
-    }
-
+class EditBookmarkDialog : DialogFragment() {
     // Declare the class variables.
     private lateinit var editBookmarkListener: EditBookmarkListener
 
@@ -65,6 +60,11 @@ class EditBookmarkDialog: DialogFragment() {
     private lateinit var newIconRadioButton: RadioButton
     private lateinit var saveButton: Button
 
+    // The public interface is used to send information back to the parent activity.
+    interface EditBookmarkListener {
+        fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap)
+    }
+
     override fun onAttach(context: Context) {
         // Run the default commands.
         super.onAttach(context)
@@ -74,7 +74,7 @@ class EditBookmarkDialog: DialogFragment() {
     }
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun bookmarkDatabaseId(databaseId: Int, favoriteIconBitmap: Bitmap): EditBookmarkDialog {
             // Create a favorite icon byte array output stream.
index 9c5a3273d64cc8efdbbfba9a9f27c41183415e49..e319de030ad3b34fdf4830c9cd04380b3f1c3154 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -54,12 +54,7 @@ import java.io.ByteArrayOutputStream
 private const val DATABASE_ID = "database_id"
 private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
 
-class EditBookmarkFolderDatabaseViewDialog: DialogFragment() {
-    // The public interface is used to send information back to the parent activity.
-    interface EditBookmarkFolderDatabaseViewListener {
-        fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap)
-    }
-
+class EditBookmarkFolderDatabaseViewDialog : DialogFragment() {
     // Declare the class variables.
     private lateinit var editBookmarkFolderDatabaseViewListener: EditBookmarkFolderDatabaseViewListener
 
@@ -70,6 +65,11 @@ class EditBookmarkFolderDatabaseViewDialog: DialogFragment() {
     private lateinit var currentIconRadioButton: RadioButton
     private lateinit var saveButton: Button
 
+    // The public interface is used to send information back to the parent activity.
+    interface EditBookmarkFolderDatabaseViewListener {
+        fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap)
+    }
+
     override fun onAttach(context: Context) {
         // Run the default commands.
         super.onAttach(context)
@@ -79,7 +79,7 @@ class EditBookmarkFolderDatabaseViewDialog: DialogFragment() {
     }
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun folderDatabaseId(databaseId: Int, favoriteIconBitmap: Bitmap): EditBookmarkFolderDatabaseViewDialog {
             // Create a favorite icon byte array output stream.
index ea4ea4d443dfe0248f8b4c76520c26024744368f..578fcd28e8ffddde7f75984376c9157bb51e4fd7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -50,12 +50,7 @@ import java.io.ByteArrayOutputStream
 private const val DATABASE_ID = "database_id"
 private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array"
 
-class EditBookmarkFolderDialog: DialogFragment() {
-    // The public interface is used to send information back to the parent activity.
-    interface EditBookmarkFolderListener {
-        fun onSaveBookmarkFolder(dialogFragment: DialogFragment?, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap?)
-    }
-
+class EditBookmarkFolderDialog : DialogFragment() {
     // Declare the class variables.
     private lateinit var editBookmarkFolderListener: EditBookmarkFolderListener
     private lateinit var bookmarksDatabaseHelper: BookmarksDatabaseHelper
@@ -66,6 +61,11 @@ class EditBookmarkFolderDialog: DialogFragment() {
     private lateinit var folderNameEditText: EditText
     private lateinit var saveButton: Button
 
+    // The public interface is used to send information back to the parent activity.
+    interface EditBookmarkFolderListener {
+        fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap)
+    }
+
     override fun onAttach(context: Context) {
         // Run the default commands.
         super.onAttach(context)
@@ -75,7 +75,7 @@ class EditBookmarkFolderDialog: DialogFragment() {
     }
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun folderDatabaseId(databaseId: Int, favoriteIconBitmap: Bitmap): EditBookmarkFolderDialog {
             // Create a favorite icon byte array output stream.
index c8f3de8984a5c7b82ad4b5cd7988b2e694d65555..80593fd915425af5bfcee7a63c258e1b21667479 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -39,15 +39,15 @@ import com.stoutner.privacybrowser.R
 // Declare the class constants.
 private const val FONT_SIZE = "font_size"
 
-class FontSizeDialog: DialogFragment() {
+class FontSizeDialog : DialogFragment() {
+    // Declare the class variables.
+    private lateinit var updateFontSizeListener: UpdateFontSizeListener
+
     // The public interface is used to send information back to the parent activity.
     interface UpdateFontSizeListener {
         fun onApplyNewFontSize(dialogFragment: DialogFragment?)
     }
 
-    // Declare the class variables.
-    private lateinit var updateFontSizeListener: UpdateFontSizeListener
-
     override fun onAttach(context: Context) {
         // Run the default commands.
         super.onAttach(context)
@@ -57,7 +57,7 @@ class FontSizeDialog: DialogFragment() {
     }
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun displayDialog(fontSize: Int): FontSizeDialog {
             // Create an arguments bundle.
index 8ed2a73f12209dbd868b5dc2491ceadf5fb0b5af..b3cf70742e4dd48751668fca47352899be355489 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -47,7 +47,7 @@ private const val HOST = "host"
 private const val REALM = "realm"
 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
 
-class HttpAuthenticationDialog: DialogFragment() {
+class HttpAuthenticationDialog : DialogFragment() {
     // Define the class variables.
     private var dismissDialog: Boolean = false
 
@@ -56,7 +56,7 @@ class HttpAuthenticationDialog: DialogFragment() {
     private lateinit var passwordEditText: EditText
 
     companion object {
-        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
         @JvmStatic
         fun displayDialog(host: String, realm: String, webViewFragmentId: Long): HttpAuthenticationDialog {
             // Create an arguments bundle.
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.java
deleted file mode 100644 (file)
index a421177..0000000
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dialogs;
-
-import android.annotation.SuppressLint;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.CursorAdapter;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.DialogFragment;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.activities.BookmarksActivity;
-import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
-
-import java.io.ByteArrayOutputStream;
-
-public class MoveToFolderDialog extends DialogFragment {
-    // Instantiate the class variables.
-    private MoveToFolderListener moveToFolderListener;
-    private BookmarksDatabaseHelper bookmarksDatabaseHelper;
-    private StringBuilder exceptFolders;
-
-    // The public interface is used to send information back to the parent activity.
-    public interface MoveToFolderListener {
-        void onMoveToFolder(DialogFragment dialogFragment);
-    }
-
-    public void onAttach(@NonNull Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for `MoveToFolderListener` from the launching context.
-        moveToFolderListener = (MoveToFolderListener) context;
-    }
-
-    // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
-    @SuppressLint("InflateParams")
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Initialize the database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
-        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(getContext(), null, null, 0);
-
-        // Use an alert dialog builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog);
-
-        // Get the current theme status.
-        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-        // Set the icon according to the theme.
-        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-            dialogBuilder.setIcon(R.drawable.move_to_folder_blue_day);
-        } else {
-            dialogBuilder.setIcon(R.drawable.move_to_folder_blue_night);
-        }
-
-        // Set the title.
-        dialogBuilder.setTitle(R.string.move_to_folder);
-
-        // Remove the incorrect lint warning that `getActivity()` might be null.
-        assert getActivity() != null;
-
-        // Set the view.  The parent view is `null` because it will be assigned by `AlertDialog`.
-        dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_dialog, null));
-
-        // Set the listener for the negative button.
-        dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
-            // Do nothing.  The `AlertDialog` will close automatically.
-        });
-
-        // Set the listener fo the positive button.
-        dialogBuilder.setPositiveButton(R.string.move, (DialogInterface dialog, int which) -> {
-            // Return the `DialogFragment` to the parent activity on save.
-            moveToFolderListener.onMoveToFolder(MoveToFolderDialog.this);
-        });
-
-        // Create an alert dialog from the alert dialog builder.
-        final AlertDialog alertDialog = dialogBuilder.create();
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
-
-        // Get the screenshot preference.
-        boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            // Remove the warning below that `getWindow()` might be null.
-            assert alertDialog.getWindow() != null;
-
-            // Disable screenshots.
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // Show the alert dialog so the items in the layout can be modified.
-        alertDialog.show();
-
-        // Get a handle for the positive button.
-        final Button moveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-
-        // Initially disable the positive button.
-        moveButton.setEnabled(false);
-
-        // Initialize the variables.
-        Cursor foldersCursor;
-        CursorAdapter foldersCursorAdapter;
-        exceptFolders = new StringBuilder();
-
-        // Check to see if we are in the `Home Folder`.
-        if (BookmarksActivity.currentFolder.isEmpty()) {  // Don't display `Home Folder` at the top of the `ListView`.
-            // If a folder is selected, add it and all children to the list of folders not to display.
-            long[] selectedBookmarksLongArray = BookmarksActivity.checkedItemIds;
-            for (long databaseIdLong : selectedBookmarksLongArray) {
-                // Get `databaseIdInt` for each selected bookmark.
-                int databaseIdInt = (int) databaseIdLong;
-
-                // If `databaseIdInt` is a folder.
-                if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
-                    // Get the name of the selected folder.
-                    String folderName = bookmarksDatabaseHelper.getFolderName(databaseIdInt);
-
-                    // Populate the list of folders not to get.
-                    if (exceptFolders.toString().isEmpty()){
-                        // Add the selected folder to the list of folders not to display.
-                        exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName));
-                    } else {
-                        // Add the selected folder to the end of the list of folders not to display.
-                        exceptFolders.append(",");
-                        exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName));
-                    }
-
-                    // Add the selected folder's subfolders to the list of folders not to display.
-                    addSubfoldersToExceptFolders(folderName);
-                }
-            }
-
-            // Get a cursor containing the folders to display.
-            foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString());
-
-            // Setup `foldersCursorAdaptor` with `this` context.  `false` disables autoRequery.
-            foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersCursor, false) {
-                @Override
-                public View newView(Context context, Cursor cursor, ViewGroup parent) {
-                    // Remove the incorrect lint warning that `.getLayoutInflater()` might be false.
-                    assert getActivity() != null;
-
-                    // Inflate the individual item layout.  `false` does not attach it to the root.
-                    return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false);
-                }
-
-                @Override
-                public void bindView(View view, Context context, Cursor cursor) {
-                    // Get the folder icon from `cursor`.
-                    byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
-                    // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
-                    Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length);
-                    // Display `folderIconBitmap` in `move_to_folder_icon`.
-                    ImageView folderIconImageView = view.findViewById(R.id.move_to_folder_icon);
-                    folderIconImageView.setImageBitmap(folderIconBitmap);
-
-                    // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`.
-                    String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
-                    TextView folderNameTextView = view.findViewById(R.id.move_to_folder_name_textview);
-                    folderNameTextView.setText(folderName);
-                }
-            };
-        } else {  // Display `Home Folder` at the top of the `ListView`.
-            // Get the home folder icon drawable and convert it to a `Bitmap`.
-            Drawable homeFolderIconDrawable = ContextCompat.getDrawable(getActivity().getApplicationContext(), R.drawable.folder_gray_bitmap);
-            BitmapDrawable homeFolderIconBitmapDrawable = (BitmapDrawable) homeFolderIconDrawable;
-            assert homeFolderIconDrawable != null;
-            Bitmap homeFolderIconBitmap = homeFolderIconBitmapDrawable.getBitmap();
-
-            // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            ByteArrayOutputStream homeFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
-            homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream);
-            byte[] homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray();
-
-            // Setup a `MatrixCursor` for the `Home Folder`.
-            String[] homeFolderMatrixCursorColumnNames = {BookmarksDatabaseHelper._ID, BookmarksDatabaseHelper.BOOKMARK_NAME, BookmarksDatabaseHelper.FAVORITE_ICON};
-            MatrixCursor homeFolderMatrixCursor = new MatrixCursor(homeFolderMatrixCursorColumnNames);
-            homeFolderMatrixCursor.addRow(new Object[]{0, getString(R.string.home_folder), homeFolderIconByteArray});
-
-            // Add the parent folder to the list of folders not to display.
-            exceptFolders.append(DatabaseUtils.sqlEscapeString(BookmarksActivity.currentFolder));
-
-            // If a folder is selected, add it and all children to the list of folders not to display.
-            long[] selectedBookmarksLongArray = BookmarksActivity.checkedItemIds;
-            for (long databaseIdLong : selectedBookmarksLongArray) {
-                // Get `databaseIdInt` for each selected bookmark.
-                int databaseIdInt = (int) databaseIdLong;
-
-                // If `databaseIdInt` is a folder.
-                if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
-                    // Get the name of the selected folder.
-                    String folderName = bookmarksDatabaseHelper.getFolderName(databaseIdInt);
-
-                    // Add the selected folder to the end of the list of folders not to display.
-                    exceptFolders.append(",");
-                    exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName));
-
-                    // Add the selected folder's subfolders to the list of folders not to display.
-                    addSubfoldersToExceptFolders(folderName);
-                }
-            }
-
-            // Get a `Cursor` containing the folders to display.
-            foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString());
-
-            // Combine `homeFolderMatrixCursor` and `foldersCursor`.
-            MergeCursor foldersMergeCursor = new MergeCursor(new Cursor[]{homeFolderMatrixCursor, foldersCursor});
-
-            // Setup `foldersCursorAdaptor`.  `false` disables autoRequery.
-            foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersMergeCursor, false) {
-                @Override
-                public View newView(Context context, Cursor cursor, ViewGroup parent) {
-                    // Remove the incorrect lint warning that `.getLayoutInflater()` might be false.
-                    assert getActivity() != null;
-
-                    // Inflate the individual item layout.  `false` does not attach it to the root.
-                    return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false);
-                }
-
-                @Override
-                public void bindView(View view, Context context, Cursor cursor) {
-                    // Get the folder icon from `cursor`.
-                    byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
-                    // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
-                    Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length);
-                    // Display `folderIconBitmap` in `move_to_folder_icon`.
-                    ImageView folderIconImageView = view.findViewById(R.id.move_to_folder_icon);
-                    folderIconImageView.setImageBitmap(folderIconBitmap);
-
-                    // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`.
-                    String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
-                    TextView folderNameTextView = view.findViewById(R.id.move_to_folder_name_textview);
-                    folderNameTextView.setText(folderName);
-                }
-            };
-        }
-
-        // Get a handle for the folders list view.
-        ListView foldersListView = alertDialog.findViewById(R.id.move_to_folder_listview);
-
-        // Remove the incorrect lint warning below that the view might be null.
-        assert foldersListView != null;
-
-        // Set the folder list view adapter.
-        foldersListView.setAdapter(foldersCursorAdapter);
-
-        // Enable the move button when a folder is selected.
-        foldersListView.setOnItemClickListener((AdapterView<?> parent, View view, int position, long id) -> {
-            // Enable the move button.
-            moveButton.setEnabled(true);
-        });
-
-        // `onCreateDialog` requires the return of an `AlertDialog`.
-        return alertDialog;
-    }
-
-    private void addSubfoldersToExceptFolders(String folderName) {
-        // Get a `Cursor` will all the immediate subfolders.
-        Cursor subfoldersCursor = bookmarksDatabaseHelper.getSubfolders(folderName);
-
-        for (int i = 0; i < subfoldersCursor.getCount(); i++) {
-            // Move `subfolderCursor` to the current item.
-            subfoldersCursor.moveToPosition(i);
-
-            // Get the name of the subfolder.
-            String subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
-
-            // Add the subfolder to `exceptFolders`.
-            exceptFolders.append(",");
-            exceptFolders.append(DatabaseUtils.sqlEscapeString(subfolderName));
-
-            // Run the same tasks for any subfolders of the subfolder.
-            addSubfoldersToExceptFolders(subfolderName);
-        }
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt
new file mode 100644 (file)
index 0000000..84bb747
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs
+
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.content.Context
+import android.content.DialogInterface
+import android.content.res.Configuration
+import android.database.Cursor
+import android.database.DatabaseUtils
+import android.database.MatrixCursor
+import android.database.MergeCursor
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.drawable.BitmapDrawable
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.AdapterView
+import android.widget.AdapterView.OnItemClickListener
+import android.widget.ImageView
+import android.widget.ListView
+import android.widget.TextView
+
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.cursoradapter.widget.CursorAdapter
+import androidx.fragment.app.DialogFragment
+import androidx.preference.PreferenceManager
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
+
+import java.io.ByteArrayOutputStream
+import java.lang.StringBuilder
+
+// Declare the class constants.
+private const val CURRENT_FOLDER = "current_folder"
+private const val SELECTED_BOOKMARKS_LONG_ARRAY = "selected_bookmarks_long_array"
+
+class MoveToFolderDialog : DialogFragment() {
+    // Declare the class variables.
+    private lateinit var moveToFolderListener: MoveToFolderListener
+    private lateinit var bookmarksDatabaseHelper: BookmarksDatabaseHelper
+    private lateinit var exceptFolders: StringBuilder
+
+    // The public interface is used to send information back to the parent activity.
+    interface MoveToFolderListener {
+        fun onMoveToFolder(dialogFragment: DialogFragment)
+    }
+
+    override fun onAttach(context: Context) {
+        // Run the default commands.
+        super.onAttach(context)
+
+        // Get a handle for the move to folder listener from the launching context.
+        moveToFolderListener = context as MoveToFolderListener
+    }
+
+    companion object {
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
+        @JvmStatic
+        fun moveBookmarks(currentFolder: String, selectedBookmarksLongArray: LongArray): MoveToFolderDialog {
+            // Create an arguments bundle.
+            val argumentsBundle = Bundle()
+
+            // Store the arguments in the bundle.
+            argumentsBundle.putString(CURRENT_FOLDER, currentFolder)
+            argumentsBundle.putLongArray(SELECTED_BOOKMARKS_LONG_ARRAY, selectedBookmarksLongArray)
+
+            // Create a new instance of the dialog.
+            val moveToFolderDialog = MoveToFolderDialog()
+
+            // And the bundle to the dialog.
+            moveToFolderDialog.arguments = argumentsBundle
+
+            // Return the new dialog.
+            return moveToFolderDialog
+        }
+    }
+
+    // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
+    @SuppressLint("InflateParams")
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Get the data from the arguments.
+        val currentFolder = requireArguments().getString(CURRENT_FOLDER)!!
+        val selectedBookmarksLongArray = requireArguments().getLongArray(SELECTED_BOOKMARKS_LONG_ARRAY)!!
+
+        // Initialize the database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in the bookmarks database helper.
+        bookmarksDatabaseHelper = BookmarksDatabaseHelper(context, null, null, 0)
+
+        // Use an alert dialog builder to create the alert dialog.
+        val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
+
+        // Get the current theme status.
+        val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+        // Set the icon according to the theme.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            dialogBuilder.setIcon(R.drawable.move_to_folder_blue_day)
+        } else {
+            dialogBuilder.setIcon(R.drawable.move_to_folder_blue_night)
+        }
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.move_to_folder)
+
+        // Set the view.  The parent view is `null` because it will be assigned by the alert dialog.
+        dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.move_to_folder_dialog, null))
+
+        // Set the listener for the cancel button.  Using `null` as the listener closes the dialog without doing anything else.
+        dialogBuilder.setNegativeButton(R.string.cancel, null)
+
+        // Set the listener fo the move button.
+        dialogBuilder.setPositiveButton(R.string.move) { _: DialogInterface?, _: Int ->
+            // Return the dialog fragment to the parent activity on move.
+            moveToFolderListener.onMoveToFolder(this)
+        }
+
+        // Create an alert dialog from the alert dialog builder.
+        val alertDialog = dialogBuilder.create()
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+        // Get the screenshot preference.
+        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+        // Disable screenshots if not allowed.
+        if (!allowScreenshots) {
+            // Disable screenshots.
+            alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+        }
+
+        // The alert dialog must be shown before items in the layout can be modified.
+        alertDialog.show()
+
+        // Get a handle for the positive button.
+        val moveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+
+        // Initially disable the positive button.
+        moveButton.isEnabled = false
+
+        // Initialize the except folders string builder.
+        exceptFolders = StringBuilder()
+
+        // Declare the cursor variables.
+        val foldersCursor: Cursor
+        val foldersCursorAdapter: CursorAdapter
+
+        // Check to see if the bookmark is currently in the home folder.
+        if (currentFolder.isEmpty()) {  // The bookmark is currently in the home folder.  Don't display `Home Folder` at the top of the list view.
+            // If a folder is selected, add it and all children to the list of folders not to display.
+            for (databaseIdLong in selectedBookmarksLongArray) {
+                // Get the database ID int for each selected bookmark.
+                val databaseIdInt = databaseIdLong.toInt()
+
+                // Check to see if the bookmark is a folder.
+                if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
+                    // Add the folder to the list of folders not to display.
+                    addFolderToExceptFolders(databaseIdInt)
+                }
+            }
+
+            // Get a cursor containing the folders to display.
+            foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString())
+
+            // Populate the folders cursor adapter.
+            foldersCursorAdapter = populateFoldersCursorAdapter(requireContext(), foldersCursor)
+        } else {  // The current folder is not directly in the home folder.  Display `Home Folder` at the top of the list view.
+            // Get the home folder icon drawable.
+            val homeFolderIconDrawable = ContextCompat.getDrawable(requireActivity().applicationContext, R.drawable.folder_gray_bitmap)
+
+            // Convert the home folder icon drawable to a bitmap drawable.
+            val homeFolderIconBitmapDrawable = homeFolderIconDrawable as BitmapDrawable
+
+            // Convert the home folder bitmap drawable to a bitmap.
+            val homeFolderIconBitmap = homeFolderIconBitmapDrawable.bitmap
+
+            // Create a home folder icon byte array output stream.
+            val homeFolderIconByteArrayOutputStream = ByteArrayOutputStream()
+
+            // Convert the home folder bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream)
+
+            // Convert the home folder icon byte array output stream to a byte array.
+            val homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray()
+
+            // Setup the home folder matrix cursor column names.
+            val homeFolderMatrixCursorColumnNames = arrayOf(BookmarksDatabaseHelper._ID, BookmarksDatabaseHelper.BOOKMARK_NAME, BookmarksDatabaseHelper.FAVORITE_ICON)
+
+            // Setup a matrix cursor for the `Home Folder`.
+            val homeFolderMatrixCursor = MatrixCursor(homeFolderMatrixCursorColumnNames)
+
+            // Add the home folder to the home folder matrix cursor.
+            homeFolderMatrixCursor.addRow(arrayOf<Any>(0, getString(R.string.home_folder), homeFolderIconByteArray))
+
+            // Add the parent folder to the list of folders not to display.
+            exceptFolders.append(DatabaseUtils.sqlEscapeString(currentFolder))
+
+            // If a folder is selected, add it and all children to the list of folders not to display.
+            for (databaseIdLong in selectedBookmarksLongArray) {
+                // Get the database ID int for each selected bookmark.
+                val databaseIdInt = databaseIdLong.toInt()
+
+                // Check to see if the bookmark is a folder.
+                if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
+                    // Add the folder to the list of folders not to display.
+                    addFolderToExceptFolders(databaseIdInt)
+                }
+            }
+
+            // Get a cursor containing the folders to display.
+            foldersCursor = bookmarksDatabaseHelper.getFoldersExcept(exceptFolders.toString())
+
+            // Combine the home folder matrix cursor and the folders cursor.
+            val foldersMergeCursor = MergeCursor(arrayOf(homeFolderMatrixCursor, foldersCursor))
+
+            // Populate the folders cursor adapter.
+            foldersCursorAdapter = populateFoldersCursorAdapter(requireContext(), foldersMergeCursor)
+        }
+
+        // Get a handle for the folders list view.
+        val foldersListView = alertDialog.findViewById<ListView>(R.id.move_to_folder_listview)!!
+
+        // Set the folder list view adapter.
+        foldersListView.adapter = foldersCursorAdapter
+
+        // Enable the move button when a folder is selected.
+        foldersListView.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
+            // Enable the move button.
+            moveButton.isEnabled = true
+        }
+
+        // Return the alert dialog.
+        return alertDialog
+    }
+
+    private fun addFolderToExceptFolders(databaseIdInt: Int) {
+        // Get the name of the selected folder.
+        val folderName = bookmarksDatabaseHelper.getFolderName(databaseIdInt)
+
+        // Populate the list of folders not to get.
+        if (exceptFolders.isEmpty()) {
+            // Add the selected folder to the list of folders not to display.
+            exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName))
+        } else {
+            // Add the selected folder to the end of the list of folders not to display.
+            exceptFolders.append(",")
+            exceptFolders.append(DatabaseUtils.sqlEscapeString(folderName))
+        }
+
+        // Add the selected folder's subfolders to the list of folders not to display.
+        addSubfoldersToExceptFolders(folderName)
+    }
+
+    private fun addSubfoldersToExceptFolders(folderName: String) {
+        // Get a cursor with all the immediate subfolders.
+        val subfoldersCursor = bookmarksDatabaseHelper.getSubfolders(folderName)
+
+        // Add each subfolder to the list of folders not to display.
+        for (i in 0 until subfoldersCursor.count) {
+            // Move the subfolder cursor to the current item.
+            subfoldersCursor.moveToPosition(i)
+
+            // Get the name of the subfolder.
+            val subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))
+
+            // Add the subfolder to except folders.
+            exceptFolders.append(",")
+            exceptFolders.append(DatabaseUtils.sqlEscapeString(subfolderName))
+
+            // Run the same tasks for any subfolders of the subfolder.
+            addSubfoldersToExceptFolders(subfolderName)
+        }
+    }
+
+    private fun populateFoldersCursorAdapter(context: Context, cursor: Cursor): CursorAdapter {
+        // Return the folders cursor adapter.
+        return object : CursorAdapter(context, cursor, false) {
+            override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
+                // Inflate the individual item layout.
+                return requireActivity().layoutInflater.inflate(R.layout.move_to_folder_item_linearlayout, parent, false)
+            }
+
+            override fun bindView(view: View, context: Context, cursor: Cursor) {
+                // Get the data from the cursor.
+                val folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON))
+                val folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))
+
+                // Get handles for the views.
+                val folderIconImageView = view.findViewById<ImageView>(R.id.move_to_folder_icon)
+                val folderNameTextView = view.findViewById<TextView>(R.id.move_to_folder_name_textview)
+
+                // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
+                val folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.size)
+
+                // Display the folder icon bitmap.
+                folderIconImageView.setImageBitmap(folderIconBitmap)
+
+                // Display the folder name.
+                folderNameTextView.text = folderName
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.java
deleted file mode 100644 (file)
index 4359486..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dialogs;
-
-import android.Manifest;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.provider.DocumentsContract;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.DialogFragment;
-import androidx.preference.PreferenceManager;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.activities.MainWebViewActivity;
-import com.stoutner.privacybrowser.helpers.DownloadLocationHelper;
-
-import java.io.File;
-
-public class OpenDialog extends DialogFragment {
-    // Define the open listener.
-    private OpenListener openListener;
-
-    // The public interface is used to send information back to the parent activity.
-    public interface OpenListener {
-        void onOpen(DialogFragment dialogFragment);
-    }
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for the open listener from the launching context.
-        openListener = (OpenListener) context;
-    }
-
-    // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
-    @SuppressLint("InflateParams")
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Get a handle for the activity and the context.
-        Activity activity = requireActivity();
-        Context context = requireContext();
-
-        // Use an alert dialog builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialog);
-
-        // Get the current theme status.
-        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-        // Set the icon according to the theme.
-        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-            dialogBuilder.setIcon(R.drawable.proxy_enabled_night);
-        } else {
-            dialogBuilder.setIcon(R.drawable.proxy_enabled_day);
-        }
-
-        // Set the title.
-        dialogBuilder.setTitle(R.string.open);
-
-        // Set the view.  The parent view is null because it will be assigned by the alert dialog.
-        dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.open_dialog, null));
-
-        // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
-        dialogBuilder.setNegativeButton(R.string.cancel, null);
-
-        // Set the open button listener.
-        dialogBuilder.setPositiveButton(R.string.open, (DialogInterface dialog, int which) -> {
-            // Return the dialog fragment to the parent activity.
-            openListener.onOpen(this);
-        });
-
-        // Create an alert dialog from the builder.
-        AlertDialog alertDialog = dialogBuilder.create();
-
-        // Remove the incorrect lint warning below that the window might be null.
-        assert alertDialog.getWindow() != null;
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-
-        // Get the screenshot preference.
-        boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // The alert dialog must be shown before items in the layout can be modified.
-        alertDialog.show();
-
-        // Get handles for the layout items.
-        EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext);
-        Button browseButton = alertDialog.findViewById(R.id.browse_button);
-        TextView fileDoesNotExistTextView = alertDialog.findViewById(R.id.file_does_not_exist_textview);
-        TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview);
-        Button openButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-
-        // Remove the incorrect lint warnings below that the views might be null.
-        assert fileNameEditText != null;
-        assert browseButton != null;
-        assert fileDoesNotExistTextView != null;
-        assert storagePermissionTextView != null;
-
-        // Update the status of the open button when the file name changes.
-        fileNameEditText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
-                // Do nothing.
-            }
-
-            @Override
-            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
-                // Do nothing.
-            }
-
-            @Override
-            public void afterTextChanged(Editable editable) {
-                // Get the current file name.
-                String fileNameString = fileNameEditText.getText().toString();
-
-                // Convert the file name string to a file.
-                File file = new File(fileNameString);
-
-                // Check to see if the file exists.
-                if (file.exists()) {  // The file exists.
-                    // Hide the notification that the file does not exist.
-                    fileDoesNotExistTextView.setVisibility(View.GONE);
-
-                    // Enable the open button.
-                    openButton.setEnabled(true);
-                } else {  // The file does not exist.
-                    // Show the notification that the file does not exist.
-                    fileDoesNotExistTextView.setVisibility(View.VISIBLE);
-
-                    // Disable the open button.
-                    openButton.setEnabled(false);
-                }
-            }
-        });
-
-        // Instantiate the download location helper.
-        DownloadLocationHelper downloadLocationHelper = new DownloadLocationHelper();
-
-        // Get the default file path.
-        String defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/";
-
-        // Display the default file path.
-        fileNameEditText.setText(defaultFilePath);
-
-        // Move the cursor to the end of the default file path.
-        fileNameEditText.setSelection(defaultFilePath.length());
-
-        // Hide the storage permission text view if the permission has already been granted.
-        if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
-            storagePermissionTextView.setVisibility(View.GONE);
-        }
-
-        // Handle clicks on the browse button.
-        browseButton.setOnClickListener((View view) -> {
-            // Create the file picker intent.
-            Intent browseIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
-
-            // Set the intent MIME type to include all files so that everything is visible.
-            browseIntent.setType("*/*");
-
-            // Set the initial directory if the minimum API >= 26.
-            if (Build.VERSION.SDK_INT >= 26) {
-                browseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
-            }
-
-            // Start the file picker.  This must be started under `activity` to that the request code is returned correctly.
-            activity.startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_OPEN_REQUEST_CODE);
-        });
-
-        // Return the alert dialog.
-        return alertDialog;
-    }
-}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.kt
new file mode 100644 (file)
index 0000000..123c6d3
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.View
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.EditText
+import android.widget.TextView
+
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.DialogFragment
+import androidx.preference.PreferenceManager
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.activities.MainWebViewActivity
+import com.stoutner.privacybrowser.helpers.DownloadLocationHelper
+
+import java.io.File
+
+class OpenDialog : DialogFragment() {
+    // Define the open listener.
+    private lateinit var openListener: OpenListener
+
+    // The public interface is used to send information back to the parent activity.
+    interface OpenListener {
+        fun onOpen(dialogFragment: DialogFragment)
+    }
+
+    override fun onAttach(context: Context) {
+        // Run the default commands.
+        super.onAttach(context)
+
+        // Get a handle for the open listener from the launching context.
+        openListener = context as OpenListener
+    }
+
+    // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
+    @SuppressLint("InflateParams")
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Use an alert dialog builder to create the alert dialog.
+        val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
+
+        // Get the current theme status.
+        val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+        // Set the icon according to the theme.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            dialogBuilder.setIcon(R.drawable.proxy_enabled_day)
+        } else {
+            dialogBuilder.setIcon(R.drawable.proxy_enabled_night)
+        }
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.open)
+
+        // Set the view.  The parent view is null because it will be assigned by the alert dialog.
+        dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.open_dialog, null))
+
+        // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
+        dialogBuilder.setNegativeButton(R.string.cancel, null)
+
+        // Set the open button listener.
+        dialogBuilder.setPositiveButton(R.string.open) { _: DialogInterface?, _: Int ->
+            // Return the dialog fragment to the parent activity.
+            openListener.onOpen(this)
+        }
+
+        // Create an alert dialog from the builder.
+        val alertDialog = dialogBuilder.create()
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+        // Get the screenshot preference.
+        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+        // Disable screenshots if not allowed.
+        if (!allowScreenshots) {
+            alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+        }
+
+        // The alert dialog must be shown before items in the layout can be modified.
+        alertDialog.show()
+
+        // Get handles for the layout items.
+        val fileNameEditText = alertDialog.findViewById<EditText>(R.id.file_name_edittext)!!
+        val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
+        val fileDoesNotExistTextView = alertDialog.findViewById<TextView>(R.id.file_does_not_exist_textview)!!
+        val storagePermissionTextView = alertDialog.findViewById<TextView>(R.id.storage_permission_textview)!!
+        val openButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+
+        // Update the status of the open button when the file name changes.
+        fileNameEditText.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
+                // Do nothing.
+            }
+
+            override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
+                // Do nothing.
+            }
+
+            override fun afterTextChanged(editable: Editable) {
+                // Get the current file name.
+                val fileNameString = fileNameEditText.text.toString()
+
+                // Convert the file name string to a file.
+                val file = File(fileNameString)
+
+                // Check to see if the file exists.
+                if (file.exists()) {  // The file exists.
+                    // Hide the notification that the file does not exist.
+                    fileDoesNotExistTextView.visibility = View.GONE
+
+                    // Enable the open button.
+                    openButton.isEnabled = true
+                } else {  // The file does not exist.
+                    // Show the notification that the file does not exist.
+                    fileDoesNotExistTextView.visibility = View.VISIBLE
+
+                    // Disable the open button.
+                    openButton.isEnabled = false
+                }
+            }
+        })
+
+        // Instantiate the download location helper.
+        val downloadLocationHelper = DownloadLocationHelper()
+
+        // Get the default file path.
+        val defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/"
+
+        // Display the default file path.
+        fileNameEditText.setText(defaultFilePath)
+
+        // Move the cursor to the end of the default file path.
+        fileNameEditText.setSelection(defaultFilePath.length)
+
+        // Hide the storage permission text view if the permission has already been granted.
+        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+            storagePermissionTextView.visibility = View.GONE
+        }
+
+        // Handle clicks on the browse button.
+        browseButton.setOnClickListener {
+            // Create the file picker intent.
+            val browseIntent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+
+            // Set the intent MIME type to include all files so that everything is visible.
+            browseIntent.type = "*/*"
+
+            // Start the file picker.  This must be started under `activity` to that the request code is returned correctly.
+            requireActivity().startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_OPEN_REQUEST_CODE)
+        }
+
+        // Return the alert dialog.
+        return alertDialog
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java
deleted file mode 100644 (file)
index 9036f9c..0000000
+++ /dev/null
@@ -1,525 +0,0 @@
-/*
- * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dialogs;
-
-import android.annotation.SuppressLint;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.ForegroundColorSpan;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import com.google.android.material.tabs.TabLayout;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.activities.MainWebViewActivity;
-import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
-import com.stoutner.privacybrowser.views.NestedScrollWebView;
-import com.stoutner.privacybrowser.views.WrapVerticalContentViewPager;
-import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
-
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.DialogFragment;  // The AndroidX dialog fragment must be used or an error is produced on API <=22.
-import androidx.viewpager.widget.PagerAdapter;
-
-public class PinnedMismatchDialog extends DialogFragment {
-    // The public interface is used to send information back to the parent activity.
-    public interface PinnedMismatchListener {
-        void pinnedErrorGoBack();
-    }
-
-    // Declare the class variables.
-    private PinnedMismatchListener pinnedMismatchListener;
-    private NestedScrollWebView nestedScrollWebView;
-    private String currentSslIssuedToCName;
-    private String currentSslIssuedToOName;
-    private String currentSslIssuedToUName;
-    private String currentSslIssuedByCName;
-    private String currentSslIssuedByOName;
-    private String currentSslIssuedByUName;
-    private Date currentSslStartDate;
-    private Date currentSslEndDate;
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for the listener from the launching context.
-        pinnedMismatchListener = (PinnedMismatchListener) context;
-    }
-
-    public static PinnedMismatchDialog displayDialog(long webViewFragmentId) {
-        // Create an arguments bundle.
-        Bundle argumentsBundle = new Bundle();
-
-        // Store the WebView fragment ID in the bundle.
-        argumentsBundle.putLong("webview_fragment_id", webViewFragmentId);
-
-        // Create a new instance of the pinned mismatch dialog.
-        PinnedMismatchDialog pinnedMismatchDialog = new PinnedMismatchDialog();
-
-        // Add the arguments bundle to the new instance.
-        pinnedMismatchDialog.setArguments(argumentsBundle);
-
-        // Make it so.
-        return pinnedMismatchDialog;
-    }
-
-    // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
-    @SuppressLint("InflateParams")
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Get the arguments.
-        Bundle arguments = getArguments();
-
-        // Remove the incorrect lint warning below that `.getArguments().getInt()` might be null.
-        assert arguments != null;
-
-        // Get the current position of this WebView fragment.
-        int webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(arguments.getLong("webview_fragment_id"));
-
-        // Get the WebView tab fragment.
-        WebViewTabFragment webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition);
-
-        // Get the fragment view.
-        View fragmentView = webViewTabFragment.getView();
-
-        // Remove the incorrect lint warning below that the fragment view might be null.
-        assert fragmentView != null;
-
-        // Get a handle for the current WebView.
-        nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
-
-        // Use an alert dialog builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog);
-
-        // Get the context.
-        Context context = getContext();
-
-        // Remove the incorrect lint warning below that the context might be null.
-        assert context != null;
-
-        // Get the favorite icon.
-        Bitmap favoriteIconBitmap = nestedScrollWebView.getFavoriteOrDefaultIcon();
-
-        // Get the default favorite icon drawable.  `ContextCompat` must be used until API >= 21.
-        Drawable defaultFavoriteIconDrawable = ContextCompat.getDrawable(context, R.drawable.world);
-
-        // Cast the favorite icon drawable to a bitmap drawable.
-        BitmapDrawable defaultFavoriteIconBitmapDrawable = (BitmapDrawable) defaultFavoriteIconDrawable;
-
-        // Remove the incorrect warning below that the favorite icon bitmap drawable might be null.
-        assert defaultFavoriteIconBitmapDrawable != null;
-
-        // Store the default icon bitmap.
-        Bitmap defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.getBitmap();
-
-        // Set the favorite icon as the dialog icon if it exists.
-        if (favoriteIconBitmap.sameAs(defaultFavoriteIconBitmap)) {  // There is no website favorite icon.
-            // Get the current theme status.
-            int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-            // Set the icon according to the theme.
-            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_night);
-            } else {
-                dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_day);
-            }
-        } else {  // There is a favorite icon.
-            // Create a drawable version of the favorite icon.
-            Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), favoriteIconBitmap);
-
-            // Set the icon.
-            dialogBuilder.setIcon(favoriteIconDrawable);
-        }
-
-        // Setup the neutral button.
-        dialogBuilder.setNeutralButton(R.string.update, (DialogInterface dialog, int which) -> {
-            // Initialize the long date variables.  If the date is null, a long value of `0` will be stored in the Domains database entry.
-            long currentSslStartDateLong = 0;
-            long currentSslEndDateLong = 0;
-
-            // Convert the `Dates` into `longs`.
-            if (currentSslStartDate != null) {
-                currentSslStartDateLong = currentSslStartDate.getTime();
-            }
-
-            if (currentSslEndDate != null) {
-                currentSslEndDateLong = currentSslEndDate.getTime();
-            }
-
-            // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
-
-            // Update the SSL certificate if it is pinned.
-            if (nestedScrollWebView.hasPinnedSslCertificate()) {
-                // Update the pinned SSL certificate in the domain database.
-                domainsDatabaseHelper.updatePinnedSslCertificate(nestedScrollWebView.getDomainSettingsDatabaseId(), currentSslIssuedToCName, currentSslIssuedToOName, currentSslIssuedToUName,
-                        currentSslIssuedByCName, currentSslIssuedByOName, currentSslIssuedByUName, currentSslStartDateLong, currentSslEndDateLong);
-
-                // Update the pinned SSL certificate in the nested scroll WebView.
-                nestedScrollWebView.setPinnedSslCertificate(currentSslIssuedToCName, currentSslIssuedToOName, currentSslIssuedToUName, currentSslIssuedByCName, currentSslIssuedByOName, currentSslIssuedByUName,
-                        currentSslStartDate, currentSslEndDate);
-            }
-
-            // Update the IP addresses if they are pinned.
-            if (nestedScrollWebView.hasPinnedIpAddresses()) {
-                // Update the pinned IP addresses in the domain database.
-                domainsDatabaseHelper.updatePinnedIpAddresses(nestedScrollWebView.getDomainSettingsDatabaseId(), nestedScrollWebView.getCurrentIpAddresses());
-
-                // Update the pinned IP addresses in the nested scroll WebView.
-                nestedScrollWebView.setPinnedIpAddresses(nestedScrollWebView.getCurrentIpAddresses());
-            }
-        });
-
-        // Setup the back button.
-        dialogBuilder.setNegativeButton(R.string.back, (DialogInterface dialog, int which) -> {
-            if (nestedScrollWebView.canGoBack()) {  // There is a back page in the history.
-                // Invoke the navigate history listener in the calling activity.  These commands cannot be run here because they need access to `applyDomainSettings()`.
-                pinnedMismatchListener.pinnedErrorGoBack();
-            } else {  // There are no pages to go back to.
-                // Load a blank page
-                nestedScrollWebView.loadUrl("");
-            }
-        });
-
-        // Setup the proceed button.
-        dialogBuilder.setPositiveButton(R.string.proceed, (DialogInterface dialog, int which) -> {
-            // Do not check the pinned information for this domain again until the domain changes.
-            nestedScrollWebView.setIgnorePinnedDomainInformation(true);
-        });
-
-        // Set the title.
-        dialogBuilder.setTitle(R.string.pinned_mismatch);
-
-        // Remove the incorrect lint warning below that `getLayoutInflater()` might be null.
-        assert getActivity() != null;
-
-        // Set the layout.  The parent view is `null` because it will be assigned by `AlertDialog`.
-        // For some reason, `getLayoutInflater()` without `getActivity()` produces an endless loop (probably a bug that will be fixed at some point in the future).
-        dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.pinned_mismatch_linearlayout, null));
-
-        // Create an alert dialog from the alert dialog builder.
-        final AlertDialog alertDialog = dialogBuilder.create();
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
-
-        // Get the screenshot preference.
-        boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            // Remove the warning below that `getWindow()` might be null.
-            assert alertDialog.getWindow() != null;
-
-            // Disable screenshots.
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // Show the alert dialog so the items in the layout can be modified.
-        alertDialog.show();
-
-        //  Get a handle for the views.
-        WrapVerticalContentViewPager wrapVerticalContentViewPager = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager);
-        TabLayout tabLayout = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_tablayout);
-
-        // Remove the incorrect lint warning below that the views might be null.
-        assert wrapVerticalContentViewPager != null;
-        assert tabLayout != null;
-
-        // Set the view pager adapter.
-        wrapVerticalContentViewPager.setAdapter(new pagerAdapter());
-
-        // Connect the tab layout to the view pager.
-        tabLayout.setupWithViewPager(wrapVerticalContentViewPager);
-
-        // Return the alert dialog.
-        return alertDialog;
-    }
-
-    private class pagerAdapter extends PagerAdapter {
-        @Override
-        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
-            // Check to see if the `View` and the `Object` are the same.
-            return (view == object);
-        }
-
-        @Override
-        public int getCount() {
-            // There are two tabs.
-            return 2;
-        }
-
-        @Override
-        public CharSequence getPageTitle(int position) {
-            // Return the current tab title.
-            if (position == 0) {  // The current SSL certificate tab.
-                return getString(R.string.current);
-            } else {  // The pinned SSL certificate tab.
-                return getString(R.string.pinned);
-            }
-        }
-
-        @Override
-        @NonNull
-        public Object instantiateItem(@NonNull ViewGroup container, int position) {
-            // Inflate the scroll view for this tab.
-            ViewGroup tabViewGroup = (ViewGroup) getLayoutInflater().inflate(R.layout.pinned_mismatch_scrollview, container, false);
-
-            // Get handles for the `TextViews`.
-            TextView domainNameTextView = tabViewGroup.findViewById(R.id.domain_name);
-            TextView ipAddressesTextView = tabViewGroup.findViewById(R.id.ip_addresses);
-            TextView issuedToCNameTextView = tabViewGroup.findViewById(R.id.issued_to_cname);
-            TextView issuedToONameTextView = tabViewGroup.findViewById(R.id.issued_to_oname);
-            TextView issuedToUNameTextView = tabViewGroup.findViewById(R.id.issued_to_uname);
-            TextView issuedByCNameTextView = tabViewGroup.findViewById(R.id.issued_by_cname);
-            TextView issuedByONameTextView = tabViewGroup.findViewById(R.id.issued_by_oname);
-            TextView issuedByUNameTextView = tabViewGroup.findViewById(R.id.issued_by_uname);
-            TextView startDateTextView = tabViewGroup.findViewById(R.id.start_date);
-            TextView endDateTextView = tabViewGroup.findViewById(R.id.end_date);
-
-            // Setup the labels.
-            String domainNameLabel = getString(R.string.domain_label) + "  ";
-            String ipAddressesLabel = getString(R.string.ip_addresses) + "  ";
-            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) + "  ";
-
-            // Convert the URL to a URI.
-            Uri currentUri = Uri.parse(nestedScrollWebView.getUrl());
-
-            // Get the current host from the URI.
-            String domainName = currentUri.getHost();
-
-            // Get the current website SSL certificate.
-            SslCertificate sslCertificate = nestedScrollWebView.getCertificate();
-
-            // Extract the individual pieces of information from the current website SSL certificate if it is not null.
-            if (sslCertificate != null) {
-                currentSslIssuedToCName = sslCertificate.getIssuedTo().getCName();
-                currentSslIssuedToOName = sslCertificate.getIssuedTo().getOName();
-                currentSslIssuedToUName = sslCertificate.getIssuedTo().getUName();
-                currentSslIssuedByCName = sslCertificate.getIssuedBy().getCName();
-                currentSslIssuedByOName = sslCertificate.getIssuedBy().getOName();
-                currentSslIssuedByUName = sslCertificate.getIssuedBy().getUName();
-                currentSslStartDate = sslCertificate.getValidNotBeforeDate();
-                currentSslEndDate = sslCertificate.getValidNotAfterDate();
-            } else {
-                // Initialize the current website SSL certificate variables with blank information.
-                currentSslIssuedToCName = "";
-                currentSslIssuedToOName = "";
-                currentSslIssuedToUName = "";
-                currentSslIssuedByCName = "";
-                currentSslIssuedByOName = "";
-                currentSslIssuedByUName = "";
-            }
-
-            // Get the pinned SSL certificate.
-            ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
-
-            // Extract the arrays from the array list.
-            String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
-            Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
-
-            // Setup the domain name spannable string builder.
-            SpannableStringBuilder domainNameStringBuilder = new SpannableStringBuilder(domainNameLabel + domainName);
-
-            // Initialize the spannable string builders.
-            SpannableStringBuilder ipAddressesStringBuilder;
-            SpannableStringBuilder issuedToCNameStringBuilder;
-            SpannableStringBuilder issuedToONameStringBuilder;
-            SpannableStringBuilder issuedToUNameStringBuilder;
-            SpannableStringBuilder issuedByCNameStringBuilder;
-            SpannableStringBuilder issuedByONameStringBuilder;
-            SpannableStringBuilder issuedByUNameStringBuilder;
-            SpannableStringBuilder startDateStringBuilder;
-            SpannableStringBuilder endDateStringBuilder;
-
-            // Setup the spannable string builders for each tab.
-            if (position == 0) {  // Setup the current settings tab.
-                // Create the string builders.
-                ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.getCurrentIpAddresses());
-                issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedToCName);
-                issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedToOName);
-                issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedToUName);
-                issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedByCName);
-                issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedByOName);
-                issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedByUName);
-
-                // Set the dates if they aren't `null`.
-                if (currentSslStartDate == null) {
-                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
-                } else {
-                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate));
-                }
-
-                if (currentSslEndDate == null) {
-                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
-                } else {
-                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate));
-                }
-            } else {  // Setup the pinned settings tab.
-                // Create the string builders.
-                ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.getPinnedIpAddresses());
-                issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + pinnedSslCertificateStringArray[0]);
-                issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + pinnedSslCertificateStringArray[1]);
-                issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + pinnedSslCertificateStringArray[2]);
-                issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + pinnedSslCertificateStringArray[3]);
-                issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + pinnedSslCertificateStringArray[4]);
-                issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + pinnedSslCertificateStringArray[5]);
-
-                // Set the dates if they aren't `null`.
-                if (pinnedSslCertificateDateArray[0] == null) {
-                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
-                } else {
-                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(pinnedSslCertificateDateArray[0]));
-                }
-
-                if (pinnedSslCertificateDateArray[1] == null) {
-                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
-                } else {
-                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(pinnedSslCertificateDateArray[1]));
-                }
-            }
-
-            // Define the color spans.
-            ForegroundColorSpan blueColorSpan;
-            ForegroundColorSpan redColorSpan;
-
-            // Get the current theme status.
-            int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-            // Set the color spans according to the theme.  The deprecated `getResources()` must be used until the minimum API >= 23.
-            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
-                redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
-            } else {
-                blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.violet_700));
-                redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
-            }
-
-            // Set the domain name to be blue.
-            domainNameStringBuilder.setSpan(blueColorSpan, domainNameLabel.length(), domainNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-
-            // Color coordinate the IP addresses if they are pinned.
-            if (nestedScrollWebView.hasPinnedIpAddresses()) {
-                if (nestedScrollWebView.getCurrentIpAddresses().equals(nestedScrollWebView.getPinnedIpAddresses())) {
-                    ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    ipAddressesStringBuilder.setSpan(redColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-            }
-
-            // Color coordinate the SSL certificate fields if they are pinned.
-            if (nestedScrollWebView.hasPinnedSslCertificate()) {
-                if (currentSslIssuedToCName.equals(pinnedSslCertificateStringArray[0])) {
-                    issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if (currentSslIssuedToOName.equals(pinnedSslCertificateStringArray[1])) {
-                    issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if (currentSslIssuedToUName.equals(pinnedSslCertificateStringArray[2])) {
-                    issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if (currentSslIssuedByCName.equals(pinnedSslCertificateStringArray[3])) {
-                    issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if (currentSslIssuedByOName.equals(pinnedSslCertificateStringArray[4])) {
-                    issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if (currentSslIssuedByUName.equals(pinnedSslCertificateStringArray[5])) {
-                    issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if ((currentSslStartDate != null) && currentSslStartDate.equals(pinnedSslCertificateDateArray[0])) {
-                    startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if ((currentSslEndDate != null) && currentSslEndDate.equals(pinnedSslCertificateDateArray[1])) {
-                    endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                } else {
-                    endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-            }
-
-            // Display the strings.
-            domainNameTextView.setText(domainNameStringBuilder);
-            ipAddressesTextView.setText(ipAddressesStringBuilder);
-            issuedToCNameTextView.setText(issuedToCNameStringBuilder);
-            issuedToONameTextView.setText(issuedToONameStringBuilder);
-            issuedToUNameTextView.setText(issuedToUNameStringBuilder);
-            issuedByCNameTextView.setText(issuedByCNameStringBuilder);
-            issuedByONameTextView.setText(issuedByONameStringBuilder);
-            issuedByUNameTextView.setText(issuedByUNameStringBuilder);
-            startDateTextView.setText(startDateStringBuilder);
-            endDateTextView.setText(endDateStringBuilder);
-
-            // Display the tab.
-            container.addView(tabViewGroup);
-
-            // Make it so.
-            return tabViewGroup;
-        }
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt
new file mode 100644 (file)
index 0000000..0dd2ce5
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs
+
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.content.Context
+import android.content.DialogInterface
+import android.content.res.Configuration
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.view.WindowManager
+
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.DialogFragment
+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.activities.MainWebViewActivity
+import com.stoutner.privacybrowser.adapters.PinnedMismatchPagerAdapter
+import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
+import com.stoutner.privacybrowser.views.NestedScrollWebView
+
+// Declare the class constants.
+private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
+
+class PinnedMismatchDialog : DialogFragment() {
+    // Declare the class variables.
+    private lateinit var pinnedMismatchListener: PinnedMismatchListener
+
+    // The public interface is used to send information back to the parent activity.
+    interface PinnedMismatchListener {
+        fun pinnedErrorGoBack()
+    }
+
+    override fun onAttach(context: Context) {
+        // Run the default commands.
+        super.onAttach(context)
+
+        // Get a handle for the listener from the launching context.
+        pinnedMismatchListener = context as PinnedMismatchListener
+    }
+
+    companion object {
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
+        @JvmStatic
+        fun displayDialog(webViewFragmentId: Long): PinnedMismatchDialog {
+            // Create an arguments bundle.
+            val argumentsBundle = Bundle()
+
+            // Store the WebView fragment ID in the bundle.
+            argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
+
+            // Create a new instance of the pinned mismatch dialog.
+            val pinnedMismatchDialog = PinnedMismatchDialog()
+
+            // Add the arguments bundle to the new instance.
+            pinnedMismatchDialog.arguments = argumentsBundle
+
+            // Make it so.
+            return pinnedMismatchDialog
+        }
+    }
+
+    // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
+    @SuppressLint("InflateParams")
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Get the WebView fragment ID.
+        val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
+
+        // Get the current position of this WebView fragment.
+        val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
+
+        // Get the WebView tab fragment.
+        val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
+
+        // Get the fragment view.
+        val fragmentView = webViewTabFragment.requireView()
+
+        // Get a handle for the current WebView.
+        val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)!!
+
+        // Use an alert dialog builder to create the alert dialog.
+        val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
+
+        // Get the favorite icon.
+        val favoriteIconBitmap = nestedScrollWebView.favoriteOrDefaultIcon
+
+        // Get the default favorite icon drawable.  `ContextCompat` must be used until API >= 21.
+        val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world)
+
+        // Cast the favorite icon drawable to a bitmap drawable.
+        val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
+
+        // Store the default icon bitmap.
+        val defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.bitmap
+
+        // Set the favorite icon as the dialog icon if it exists.
+        if (favoriteIconBitmap.sameAs(defaultFavoriteIconBitmap)) {  // There is no website favorite icon.
+            // Get the current theme status.
+            val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+            // Set the icon according to the theme.
+            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_day)
+            } else {
+                dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_night)
+            }
+        } else {  // There is a favorite icon.
+            // Create a drawable version of the favorite icon.
+            val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
+
+            // Set the icon.
+            dialogBuilder.setIcon(favoriteIconDrawable)
+        }
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.pinned_mismatch)
+
+        // Set the layout.  The parent view is `null` because it will be assigned by the alert dialog.
+        dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.pinned_mismatch_linearlayout, null))
+
+        // Set the update button listener.
+        dialogBuilder.setNeutralButton(R.string.update) { _: DialogInterface?, _: Int ->
+            // Get the current SSL certificate.
+            val currentSslCertificate = nestedScrollWebView.certificate!!
+
+            // Get the dates from the certificate.
+            val currentSslStartDate = currentSslCertificate.validNotBeforeDate
+            val currentSslEndDate = currentSslCertificate.validNotAfterDate
+
+            // Convert the dates into longs.  If the date is null, a long value of `0` will be stored in the domains database entry.
+            val currentSslStartDateLong: Long = currentSslStartDate?.time ?: 0
+            val currentSslEndDateLong: Long = currentSslEndDate?.time ?: 0
+
+            // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in the domains database helper.
+            val domainsDatabaseHelper = DomainsDatabaseHelper(context, null, null, 0)
+
+            // Update the SSL certificate if it is pinned.
+            if (nestedScrollWebView.hasPinnedSslCertificate()) {
+                // Update the pinned SSL certificate in the domain database.
+                domainsDatabaseHelper.updatePinnedSslCertificate(nestedScrollWebView.domainSettingsDatabaseId, currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName,
+                        currentSslCertificate.issuedTo.uName, currentSslCertificate.issuedBy.cName, currentSslCertificate.issuedBy.oName, currentSslCertificate.issuedBy.uName, currentSslStartDateLong,
+                        currentSslEndDateLong)
+
+                // Update the pinned SSL certificate in the nested scroll WebView.
+                nestedScrollWebView.setPinnedSslCertificate(currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName, currentSslCertificate.issuedTo.uName,
+                        currentSslCertificate.issuedBy.cName, currentSslCertificate.issuedBy.oName, currentSslCertificate.issuedBy.uName, currentSslStartDate, currentSslEndDate)
+            }
+
+            // Update the IP addresses if they are pinned.
+            if (nestedScrollWebView.hasPinnedIpAddresses()) {
+                // Update the pinned IP addresses in the domain database.
+                domainsDatabaseHelper.updatePinnedIpAddresses(nestedScrollWebView.domainSettingsDatabaseId, nestedScrollWebView. currentIpAddresses)
+
+                // Update the pinned IP addresses in the nested scroll WebView.
+                nestedScrollWebView.pinnedIpAddresses = nestedScrollWebView.currentIpAddresses
+            }
+        }
+
+        // Set the back button listener.
+        dialogBuilder.setNegativeButton(R.string.back) { _: DialogInterface?, _: Int ->
+            if (nestedScrollWebView.canGoBack()) {  // There is a back page in the history.
+                // Invoke the navigate history listener in the calling activity.  These commands cannot be run here because they need access to `applyDomainSettings()`.
+                pinnedMismatchListener.pinnedErrorGoBack()
+            } else {  // There are no pages to go back to.
+                // Load a blank page
+                nestedScrollWebView.loadUrl("")
+            }
+        }
+
+        // Set the proceed button listener.
+        dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
+            // Do not check the pinned information for this domain again until the domain changes.
+            nestedScrollWebView.setIgnorePinnedDomainInformation(true)
+        }
+
+        // Create an alert dialog from the alert dialog builder.
+        val alertDialog = dialogBuilder.create()
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+        // Get the screenshot preference.
+        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+        // Disable screenshots if not allowed.
+        if (!allowScreenshots) {
+            // Disable screenshots.
+            alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+        }
+
+        // The alert dialog must be shown before items in the layout can be modified.
+        alertDialog.show()
+
+        //  Get handles for the views.
+        val viewPager = alertDialog.findViewById<ViewPager>(R.id.pinned_ssl_certificate_mismatch_viewpager)!!
+        val tabLayout = alertDialog.findViewById<TabLayout>(R.id.pinned_ssl_certificate_mismatch_tablayout)!!
+
+        // Initialize the pinned mismatch pager adapter.
+        val pinnedMismatchPagerAdapter = PinnedMismatchPagerAdapter(requireContext(), layoutInflater, webViewFragmentId)
+
+        // Set the view pager adapter.
+        viewPager.adapter = pinnedMismatchPagerAdapter
+
+        // Connect the tab layout to the view pager.
+        tabLayout.setupWithViewPager(viewPager)
+
+        // Return the alert dialog.
+        return alertDialog
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.java
deleted file mode 100644 (file)
index 9b70e0d..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dialogs;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.preference.PreferenceManager;
-
-
-import com.stoutner.privacybrowser.helpers.ProxyHelper;
-import com.stoutner.privacybrowser.R;
-
-public class ProxyNotInstalledDialog extends DialogFragment {
-    public static ProxyNotInstalledDialog displayDialog(String proxyMode) {
-        // Create an arguments bundle.
-        Bundle argumentsBundle = new Bundle();
-
-        // Store the proxy mode in the bundle.
-        argumentsBundle.putString("proxy_mode", proxyMode);
-
-        // Create a new instance of the dialog.
-        ProxyNotInstalledDialog proxyNotInstalledDialog = new ProxyNotInstalledDialog();
-
-        // Add the bundle to the dialog.
-        proxyNotInstalledDialog.setArguments(argumentsBundle);
-
-        // Return the new dialog.
-        return proxyNotInstalledDialog;
-    }
-
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Get the context.
-        Context context = requireContext();
-
-        // Get the arguments.
-        Bundle arguments = getArguments();
-
-        // Remove the incorrect lint warning below that the arguments might be null.
-        assert arguments != null;
-
-        // Get the proxy mode from the arguments.
-        String proxyMode = arguments.getString("proxy_mode");
-
-        // Remove the incorrect lint warning below that the proxy mode might be null.
-        assert proxyMode != null;
-
-        // Use a builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialog);
-
-        // Get the current theme status.
-        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-        // Set the icon according to the theme.
-        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-            dialogBuilder.setIcon(R.drawable.proxy_enabled_night);
-        } else {
-            dialogBuilder.setIcon(R.drawable.proxy_enabled_day);
-        }
-
-        // Set the title and the message according to the proxy mode.
-        switch (proxyMode) {
-            case ProxyHelper.TOR:
-                // Set the title.
-                dialogBuilder.setTitle(R.string.orbot_not_installed_title);
-
-                // Set the message.
-                dialogBuilder.setMessage(R.string.orbot_not_installed_message);
-                break;
-
-            case ProxyHelper.I2P:
-                // Set the title.
-                dialogBuilder.setTitle(R.string.i2p_not_installed_title);
-
-                // Set the message.
-                dialogBuilder.setMessage(R.string.i2p_not_installed_message);
-                break;
-        }
-
-        // Set the positive button.
-        dialogBuilder.setPositiveButton(R.string.close, (DialogInterface dialog, int which) -> {
-            // Do nothing.  The alert dialog will close automatically.
-        });
-
-        // Create an alert dialog from the alert dialog builder.
-        AlertDialog alertDialog = dialogBuilder.create();
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-
-        // Get the screenshot preference.
-        boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            // Remove the warning below that `getWindows()` might be null.
-            assert alertDialog.getWindow() != null;
-
-            // Disable screenshots.
-            alertDialog.getWindow().addFlags((WindowManager.LayoutParams.FLAG_SECURE));
-        }
-
-        // Return the alert dialog.
-        return alertDialog;
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.kt
new file mode 100644 (file)
index 0000000..d8ed9f1
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs
+
+import android.app.Dialog
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.WindowManager
+
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.preference.PreferenceManager
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.helpers.ProxyHelper
+
+// Declare the class constants.
+private const val PROXY_MODE = "proxy_mode"
+
+class ProxyNotInstalledDialog : DialogFragment() {
+    companion object {
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
+        @JvmStatic
+        fun displayDialog(proxyMode: String): ProxyNotInstalledDialog {
+            // Create an arguments bundle.
+            val argumentsBundle = Bundle()
+
+            // Store the proxy mode in the bundle.
+            argumentsBundle.putString(PROXY_MODE, proxyMode)
+
+            // Create a new instance of the dialog.
+            val proxyNotInstalledDialog = ProxyNotInstalledDialog()
+
+            // Add the bundle to the dialog.
+            proxyNotInstalledDialog.arguments = argumentsBundle
+
+            // Return the new dialog.
+            return proxyNotInstalledDialog
+        }
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Get the proxy mode from the arguments.
+        val proxyMode = requireArguments().getString(PROXY_MODE)!!
+
+        // Use a builder to create the alert dialog.
+        val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
+
+        // Get the current theme status.
+        val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+        // Set the icon according to the theme.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            dialogBuilder.setIcon(R.drawable.proxy_enabled_day)
+        } else {
+            dialogBuilder.setIcon(R.drawable.proxy_enabled_night)
+        }
+
+        // Set the title and the message according to the proxy mode.
+        when (proxyMode) {
+            ProxyHelper.TOR -> {
+                // Set the title.
+                dialogBuilder.setTitle(R.string.orbot_not_installed_title)
+
+                // Set the message.
+                dialogBuilder.setMessage(R.string.orbot_not_installed_message)
+            }
+
+            ProxyHelper.I2P -> {
+                // Set the title.
+                dialogBuilder.setTitle(R.string.i2p_not_installed_title)
+
+                // Set the message.
+                dialogBuilder.setMessage(R.string.i2p_not_installed_message)
+            }
+        }
+
+        // Set the close button listener.  Using `null` as the listener closes the dialog without doing anything else.
+        dialogBuilder.setPositiveButton(R.string.close, null)
+
+        // Create an alert dialog from the alert dialog builder.
+        val alertDialog = dialogBuilder.create()
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+        // Get the screenshot preference.
+        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+        // Disable screenshots if not allowed.
+        if (!allowScreenshots) {
+            alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+        }
+
+        // Return the alert dialog.
+        return alertDialog
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java
deleted file mode 100644 (file)
index 2b591f7..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dialogs;
-
-import android.Manifest;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.preference.PreferenceManager;
-import android.provider.DocumentsContract;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.DialogFragment;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.helpers.DownloadLocationHelper;
-
-import java.io.File;
-
-public class SaveDialog extends DialogFragment {
-    // Declare the save listener.
-    private SaveListener saveListener;
-
-    // The public interface is used to send information back to the parent activity.
-    public interface SaveListener {
-        void onSave(int saveType, DialogFragment dialogFragment);
-    }
-
-    // Declare the class constants.
-    public static final int SAVE_LOGCAT = 0;
-    public static final int SAVE_ABOUT_VERSION_TEXT = 1;
-    public static final int SAVE_ABOUT_VERSION_IMAGE = 2;
-    private static final String SAVE_TYPE = "save_type";
-
-    // Declare the class variables.
-    String fileName;
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for save listener from the launching context.
-        saveListener = (SaveListener) context;
-    }
-
-    public static SaveDialog save(int saveType) {
-        // Create an arguments bundle.
-        Bundle argumentsBundle = new Bundle();
-
-        // Store the arguments in the bundle.
-        argumentsBundle.putInt(SAVE_TYPE, saveType);
-
-        // Create a new instance of the save dialog.
-        SaveDialog saveDialog = new SaveDialog();
-
-        // Add the arguments bundle to the dialog.
-        saveDialog.setArguments(argumentsBundle);
-
-        // Return the new dialog.
-        return saveDialog;
-    }
-
-    // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
-    @SuppressLint("InflateParams")
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Get a handle for the arguments.
-        Bundle arguments = getArguments();
-
-        // Remove the incorrect lint warning that the arguments might be null.
-        assert arguments != null;
-
-        // Get the arguments from the bundle.
-        int saveType = arguments.getInt(SAVE_TYPE);
-
-        // Get a handle for the activity and the context.
-        Activity activity = requireActivity();
-        Context context = requireContext();
-
-        // Use an alert dialog builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialog);
-
-        // Get the current theme status.
-        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-        // Set the title and icon according to the type.
-        switch (saveType) {
-            case SAVE_LOGCAT:
-                // Set the title.
-                dialogBuilder.setTitle(R.string.save_logcat);
-
-                // Set the icon according to the theme.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    dialogBuilder.setIcon(R.drawable.save_dialog_day);
-                } else {
-                    dialogBuilder.setIcon(R.drawable.save_dialog_night);
-                }
-                break;
-
-            case SAVE_ABOUT_VERSION_TEXT:
-                // Set the title.
-                dialogBuilder.setTitle(R.string.save_text);
-
-                // Set the icon according to the theme.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    dialogBuilder.setIcon(R.drawable.save_text_blue_day);
-                } else {
-                    dialogBuilder.setIcon(R.drawable.save_text_blue_night);
-                }
-                break;
-
-            case SAVE_ABOUT_VERSION_IMAGE:
-                // Set the title.
-                dialogBuilder.setTitle(R.string.save_image);
-
-                // Set the icon according to the theme.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    dialogBuilder.setIcon(R.drawable.images_enabled_day);
-                } else {
-                    dialogBuilder.setIcon(R.drawable.images_enabled_night);
-                }
-                break;
-        }
-
-        // Set the view.  The parent view is null because it will be assigned by the alert dialog.
-        dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_dialog, null));
-
-        // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
-        dialogBuilder.setNegativeButton(R.string.cancel, null);
-
-        // Set the save button listener.
-        dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> {
-            // Return the dialog fragment to the parent activity.
-            saveListener.onSave(saveType, this);
-        });
-
-        // Create an alert dialog from the builder.
-        AlertDialog alertDialog = dialogBuilder.create();
-
-        // Remove the incorrect lint warning below that `getWindow()` might be null.
-        assert alertDialog.getWindow() != null;
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
-
-        // Get the screenshot preference.
-        boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
-
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // The alert dialog must be shown before items in the layout can be modified.
-        alertDialog.show();
-
-        // Get handles for the layout items.
-        EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext);
-        Button browseButton = alertDialog.findViewById(R.id.browse_button);
-        TextView fileExistsWarningTextView = alertDialog.findViewById(R.id.file_exists_warning_textview);
-        TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview);
-        Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-
-        // Remove the incorrect lint warnings below that the views might be null.
-        assert fileNameEditText != null;
-        assert browseButton != null;
-        assert fileExistsWarningTextView != null;
-        assert storagePermissionTextView != null;
-
-        // Update the status of the save button when the file name changes.
-        fileNameEditText.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 current file name.
-                String fileNameString = fileNameEditText.getText().toString();
-
-                // Convert the file name string to a file.
-                File file = new File(fileNameString);
-
-                // Check to see if the file exists.
-                if (file.exists()) {
-                    // Show the file exists warning.
-                    fileExistsWarningTextView.setVisibility(View.VISIBLE);
-                } else {
-                    // Hide the file exists warning.
-                    fileExistsWarningTextView.setVisibility(View.GONE);
-                }
-
-                // Enable the save button if the file name is populated.
-                saveButton.setEnabled(!fileNameString.isEmpty());
-            }
-        });
-
-        // Set the file name according to the type.
-        switch (saveType) {
-            case SAVE_LOGCAT:
-                // Use a file name ending in `.txt`.
-                fileName = getString(R.string.privacy_browser_logcat_txt);
-                break;
-
-            case SAVE_ABOUT_VERSION_TEXT:
-                // Use a file name ending in `.txt`.
-                fileName = getString(R.string.privacy_browser_version_txt);
-                break;
-
-            case SAVE_ABOUT_VERSION_IMAGE:
-                // Use a file name ending in `.png`.
-                fileName = getString(R.string.privacy_browser_version_png);
-                break;
-        }
-
-        // Instantiate the download location helper.
-        DownloadLocationHelper downloadLocationHelper = new DownloadLocationHelper();
-
-        // Get the default file path.
-        String defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/" + fileName;
-
-        // Display the default file path.
-        fileNameEditText.setText(defaultFilePath);
-
-        // Hide the storage permission text view if the permission has already been granted.
-        if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
-            storagePermissionTextView.setVisibility(View.GONE);
-        }
-
-        // Handle clicks on the browse button.
-        browseButton.setOnClickListener((View view) -> {
-            // Create the file picker intent.
-            Intent browseIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-
-            // Set the intent MIME type to include all files so that everything is visible.
-            browseIntent.setType("*/*");
-
-            // Set the initial file name.
-            browseIntent.putExtra(Intent.EXTRA_TITLE, fileName);
-
-            // Set the initial directory if the minimum API >= 26.
-            if (Build.VERSION.SDK_INT >= 26) {
-                browseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
-            }
-
-            // Request a file that can be opened.
-            browseIntent.addCategory(Intent.CATEGORY_OPENABLE);
-
-            // Launch the file picker.  There is only one `startActivityForResult()`, so the request code is simply set to 0, but it must be run under `activity` so the request code is correct.
-            activity.startActivityForResult(browseIntent, 0);
-        });
-
-        // Return the alert dialog.
-        return alertDialog;
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt
new file mode 100644 (file)
index 0000000..d643aa7
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.app.Dialog
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.view.View
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.EditText
+import android.widget.TextView
+import android.text.TextWatcher
+import android.text.Editable
+
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.DialogFragment
+import androidx.preference.PreferenceManager
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.helpers.DownloadLocationHelper
+
+import java.io.File
+
+// Declare the class constants.
+private const val SAVE_TYPE = "save_type"
+
+class SaveDialog : DialogFragment() {
+    // Declare the class variables.
+    private lateinit var saveListener: SaveListener
+    private lateinit var fileName: String
+
+    // The public interface is used to send information back to the parent activity.
+    interface SaveListener {
+        fun onSave(saveType: Int, dialogFragment: DialogFragment)
+    }
+
+    override fun onAttach(context: Context) {
+        // Run the default commands.
+        super.onAttach(context)
+
+        // Get a handle for the save listener from the launching context.
+        saveListener = context as SaveListener
+    }
+
+    companion object {
+        // Declare the companion object constants.  These can be moved to class constants once all of the code has transitioned to Kotlin.
+        const val SAVE_LOGCAT = 0
+        const val SAVE_ABOUT_VERSION_TEXT = 1
+        const val SAVE_ABOUT_VERSION_IMAGE = 2
+
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
+        @JvmStatic
+        fun save(saveType: Int): SaveDialog {
+            // Create an arguments bundle.
+            val argumentsBundle = Bundle()
+
+            // Store the arguments in the bundle.
+            argumentsBundle.putInt(SAVE_TYPE, saveType)
+
+            // Create a new instance of the save dialog.
+            val saveDialog = SaveDialog()
+
+            // Add the arguments bundle to the dialog.
+            saveDialog.arguments = argumentsBundle
+
+            // Return the new dialog.
+            return saveDialog
+        }
+    }
+
+    // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
+    @SuppressLint("InflateParams")
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Get the arguments from the bundle.
+        val saveType = requireArguments().getInt(SAVE_TYPE)
+
+        // Use an alert dialog builder to create the alert dialog.
+        val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
+
+        // Get the current theme status.
+        val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+        // Set the title and the icon according to the save type.
+        when (saveType) {
+            SAVE_LOGCAT -> {
+                // Set the title.
+                dialogBuilder.setTitle(R.string.save_logcat)
+
+                // Set the icon according to the theme.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                    dialogBuilder.setIcon(R.drawable.save_dialog_day)
+                } else {
+                    dialogBuilder.setIcon(R.drawable.save_dialog_night)
+                }
+            }
+
+            SAVE_ABOUT_VERSION_TEXT -> {
+                // Set the title.
+                dialogBuilder.setTitle(R.string.save_text)
+
+                // Set the icon according to the theme.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                    dialogBuilder.setIcon(R.drawable.save_text_blue_day)
+                } else {
+                    dialogBuilder.setIcon(R.drawable.save_text_blue_night)
+                }
+            }
+
+            SAVE_ABOUT_VERSION_IMAGE -> {
+                // Set the title.
+                dialogBuilder.setTitle(R.string.save_image)
+
+                // Set the icon according to the theme.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                    dialogBuilder.setIcon(R.drawable.images_enabled_day)
+                } else {
+                    dialogBuilder.setIcon(R.drawable.images_enabled_night)
+                }
+            }
+        }
+
+        // Set the view.  The parent view is null because it will be assigned by the alert dialog.
+        dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.save_dialog, null))
+
+        // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
+        dialogBuilder.setNegativeButton(R.string.cancel, null)
+
+        // Set the save button listener.
+        dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface?, _: Int ->
+            // Return the dialog fragment to the parent activity.
+            saveListener.onSave(saveType, this)
+        }
+
+        // Create an alert dialog from the builder.
+        val alertDialog = dialogBuilder.create()
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+        // Get the screenshot preference.
+        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+        // Disable screenshots if not allowed.
+        if (!allowScreenshots) {
+            alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+        }
+
+        // The alert dialog must be shown before items in the layout can be modified.
+        alertDialog.show()
+
+        // Get handles for the layout items.
+        val fileNameEditText = alertDialog.findViewById<EditText>(R.id.file_name_edittext)!!
+        val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
+        val fileExistsWarningTextView = alertDialog.findViewById<TextView>(R.id.file_exists_warning_textview)!!
+        val storagePermissionTextView = alertDialog.findViewById<TextView>(R.id.storage_permission_textview)!!
+        val saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+
+        // Update the status of the save button when the file name changes.
+        fileNameEditText.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 current file name.
+                val fileNameString = fileNameEditText.text.toString()
+
+                // Convert the file name string to a file.
+                val file = File(fileNameString)
+
+                // Check to see if the file exists.
+                if (file.exists()) {
+                    // Show the file exists warning.
+                    fileExistsWarningTextView.visibility = View.VISIBLE
+                } else {
+                    // Hide the file exists warning.
+                    fileExistsWarningTextView.visibility = View.GONE
+                }
+
+                // Enable the save button if the file name is populated.
+                saveButton.isEnabled = fileNameString.isNotEmpty()
+            }
+        })
+
+        // Set the file name according to the type.
+        when (saveType) {
+            SAVE_LOGCAT -> fileName = getString(R.string.privacy_browser_logcat_txt)
+            SAVE_ABOUT_VERSION_TEXT -> fileName = getString(R.string.privacy_browser_version_txt)
+            SAVE_ABOUT_VERSION_IMAGE -> fileName = getString(R.string.privacy_browser_version_png)
+        }
+
+        // Instantiate the download location helper.
+        val downloadLocationHelper = DownloadLocationHelper()
+
+        // Get the default file path.
+        val defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/" + fileName
+
+        // Display the default file path.
+        fileNameEditText.setText(defaultFilePath)
+
+        // Hide the storage permission text view if the permission has already been granted.
+        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+            storagePermissionTextView.visibility = View.GONE
+        }
+
+        // Handle clicks on the browse button.
+        browseButton.setOnClickListener {
+            // Create the file picker intent.
+            val browseIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
+
+            // Set the intent MIME type to include all files so that everything is visible.
+            browseIntent.type = "*/*"
+
+            // Set the initial file name.
+            browseIntent.putExtra(Intent.EXTRA_TITLE, fileName)
+
+            // Request a file that can be opened.
+            browseIntent.addCategory(Intent.CATEGORY_OPENABLE)
+
+            // Launch the file picker.  There is only one `startActivityForResult()`, so the request code is simply set to 0, but it must be run under `activity` so the request code is correct.
+            requireActivity().startActivityForResult(browseIntent, 0)
+        }
+
+        // Return the alert dialog.
+        return alertDialog
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.java
deleted file mode 100644 (file)
index 78d318a..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-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.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 AboutWebViewFragment extends Fragment {
-    // Declare the class constants.
-    final static String TAB_NUMBER = "tab_number";
-
-    // Declare the class variables.
-    private int tabNumber;
-
-    // Declare the class views.
-    private View webViewLayout;
-
-    public static AboutWebViewFragment createTab(int tabNumber) {
-        // Create an arguments bundle.
-        Bundle argumentsBundle = new Bundle();
-
-        // Store the arguments in the bundle.
-        argumentsBundle.putInt(TAB_NUMBER, tabNumber);
-
-        // Create a new instance of the tab fragment.
-        AboutWebViewFragment aboutWebViewFragment = new AboutWebViewFragment();
-
-        // Add the arguments bundle to the fragment.
-        aboutWebViewFragment.setArguments(argumentsBundle);
-
-        // Return the new fragment.
-        return aboutWebViewFragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        // Run the default commands.
-        super.onCreate(savedInstanceState);
-
-        // Get a handle for the arguments.
-        Bundle arguments = getArguments();
-
-        // Remove the incorrect 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 layoutInflater, ViewGroup container, Bundle savedInstanceState) {
-        // Inflate the layout.  Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.  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.
-        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 the 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, String url) {
-                // Have the WebView asset loader process the request.  This allows the loading of SVG files, which otherwise is prevented by the CORS policy.
-                return webViewAssetLoader.shouldInterceptRequest(Uri.parse(url));
-            }
-        });
-
-        // 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, with the WebView tabs starting at 1.
-        switch (tabNumber) {
-            case 1:
-                // Load the Permissions tab.
-                tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_permissions.html");
-                break;
-
-            case 2:
-                // Load the Privacy Policy tab.
-                tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_privacy_policy.html");
-                break;
-
-            case 3:
-                // Load the Changelog tab.
-                tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_changelog.html");
-                break;
-
-            case 4:
-                // Load the Licenses tab.
-                tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_licenses.html");
-                break;
-
-            case 5:
-                // Load the Contributors tab.
-                tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_contributors.html");
-                break;
-
-            case 6:
-                // Load the Links tab.
-                tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_links.html");
-                break;
-        }
-
-        // Scroll the tab if the saved instance state is not null.
-        if (savedInstanceState != null) {
-            tabWebView.post(() -> {
-                tabWebView.setScrollX(savedInstanceState.getInt("scroll_x"));
-                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 positions if the layout is not null, which can happen if a tab is not currently selected.
-        if (tabWebView != null) {
-            savedInstanceState.putInt("scroll_x", tabWebView.getScrollX());
-            savedInstanceState.putInt("scroll_y", tabWebView.getScrollY());
-        }
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt
new file mode 100644 (file)
index 0000000..0a93fb0
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.fragments
+
+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.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
+
+// Declare the class constants.
+private const val TAB_NUMBER = "tab_number"
+
+class AboutWebViewFragment : Fragment() {
+    // Declare the class variables.
+    private var tabNumber = 0
+
+    // Declare the class views.
+    private lateinit var webViewLayout: View
+
+    companion object {
+        fun createTab(tabNumber: Int): AboutWebViewFragment {
+            // Create an arguments bundle.
+            val argumentsBundle = Bundle()
+
+            // Store the arguments in the bundle.
+            argumentsBundle.putInt(TAB_NUMBER, tabNumber)
+
+            // Create a new instance of the tab fragment.
+            val aboutWebViewFragment = AboutWebViewFragment()
+
+            // Add the arguments bundle to the fragment.
+            aboutWebViewFragment.arguments = argumentsBundle
+
+            // Return the new fragment.
+            return aboutWebViewFragment
+        }
+    }
+
+    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.  False does not attach the inflated layout as a child of container.  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.
+        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() {
+            // `shouldOverrideUrlLoading` allows the sending of external links back to the main Privacy Browser WebView.  The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
+            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)
+                return true
+            }
+
+            override fun shouldInterceptRequest(webView: WebView, url: String): WebResourceResponse? {
+                // Have the WebView asset loader process the request.  This allows the loading of SVG files, which otherwise is prevented by the CORS policy.
+                return webViewAssetLoader.shouldInterceptRequest(Uri.parse(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.
+        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.settings, WebSettingsCompat.FORCE_DARK_ON)
+        }
+
+        // Load the indicated tab.  The tab numbers start at 0, with the WebView tabs starting at 1.
+        when (tabNumber) {
+            1 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_permissions.html")
+            2 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_privacy_policy.html")
+            3 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_changelog.html")
+            4 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_licenses.html")
+            5 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_contributors.html")
+            6 -> tabWebView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.android_asset_path) + "/about_links.html")
+        }
+
+        // Scroll the tab if the saved instance state is not null.
+        if (savedInstanceState != null) {
+            tabWebView.post {
+                tabWebView.scrollX = savedInstanceState.getInt("scroll_x")
+                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 positions if the layout is not null, which can happen if a tab is not currently selected.
+        if (tabWebView != null) {
+            savedInstanceState.putInt("scroll_x", tabWebView.scrollX)
+            savedInstanceState.putInt("scroll_y", tabWebView.scrollY)
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/pinned_mismatch_scrollview.xml b/app/src/main/res/layout/pinned_mismatch_scrollview.xml
deleted file mode 100644 (file)
index 725ba34..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
-  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="wrap_content"
-    android:layout_width="wrap_content"
-    android:padding="10dp"
-    android:orientation="vertical" >
-
-    <!-- Domain. -->
-    <TextView
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_marginTop="5dp"
-        android:text="@string/domain"
-        android:textAllCaps="true"
-        android:textStyle="bold"
-        android:textColor="?attr/blueTitleTextColor" />
-
-    <TextView
-        android:id="@+id/domain_name"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-
-    <TextView
-        android:id="@+id/ip_addresses"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"/>
-
-
-    <!-- Issued To. -->
-    <TextView
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_marginTop="15dp"
-        android:text="@string/issued_to"
-        android:textAllCaps="true"
-        android:textStyle="bold"
-        android:textColor="?attr/blueTitleTextColor" />
-
-    <TextView
-        android:id="@+id/issued_to_cname"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-
-    <TextView
-        android:id="@+id/issued_to_oname"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-
-    <TextView
-        android:id="@+id/issued_to_uname"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-
-
-    <!-- Issued By. -->
-    <TextView
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_marginTop="15dp"
-        android:text="@string/issued_by"
-        android:textAllCaps="true"
-        android:textStyle="bold"
-        android:textColor="?attr/blueTitleTextColor" />
-
-    <TextView
-        android:id="@+id/issued_by_cname"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-
-    <TextView
-        android:id="@+id/issued_by_oname"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-
-    <TextView
-        android:id="@+id/issued_by_uname"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-
-
-    <!-- Valid Dates. -->
-    <TextView
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_marginTop="15dp"
-        android:text="@string/valid_dates"
-        android:textAllCaps="true"
-        android:textStyle="bold"
-        android:textColor="?attr/blueTitleTextColor" />
-
-    <TextView
-        android:id="@+id/start_date"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-
-    <TextView
-        android:id="@+id/end_date"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" />
-</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/pinned_mismatch_tab_linearlayout.xml b/app/src/main/res/layout/pinned_mismatch_tab_linearlayout.xml
new file mode 100644 (file)
index 0000000..9894739
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:padding="10dp"
+    android:orientation="vertical" >
+
+    <!-- Domain. -->
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="5dp"
+        android:text="@string/domain"
+        android:textAllCaps="true"
+        android:textStyle="bold"
+        android:textColor="?attr/blueTitleTextColor" />
+
+    <TextView
+        android:id="@+id/domain_name"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+    <TextView
+        android:id="@+id/ip_addresses"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"/>
+
+
+    <!-- Issued To. -->
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="15dp"
+        android:text="@string/issued_to"
+        android:textAllCaps="true"
+        android:textStyle="bold"
+        android:textColor="?attr/blueTitleTextColor" />
+
+    <TextView
+        android:id="@+id/issued_to_cname"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+    <TextView
+        android:id="@+id/issued_to_oname"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+    <TextView
+        android:id="@+id/issued_to_uname"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+
+    <!-- Issued By. -->
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="15dp"
+        android:text="@string/issued_by"
+        android:textAllCaps="true"
+        android:textStyle="bold"
+        android:textColor="?attr/blueTitleTextColor" />
+
+    <TextView
+        android:id="@+id/issued_by_cname"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+    <TextView
+        android:id="@+id/issued_by_oname"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+    <TextView
+        android:id="@+id/issued_by_uname"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+
+    <!-- Valid Dates. -->
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="15dp"
+        android:text="@string/valid_dates"
+        android:textAllCaps="true"
+        android:textStyle="bold"
+        android:textColor="?attr/blueTitleTextColor" />
+
+    <TextView
+        android:id="@+id/start_date"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+    <TextView
+        android:id="@+id/end_date"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+</LinearLayout>
\ No newline at end of file
index 1166d2f80435ae4ad423c0cdbe58f2c204568647..d60bf091dab17811e80a254a90851acab456c9d7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2018,2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -18,6 +18,7 @@
  */
 
 package com.stoutner.privacybrowser.helpers;
+import android.app.Activity;
 import android.content.Context;
 import android.view.View;
 
@@ -25,11 +26,11 @@ import androidx.fragment.app.FragmentManager;
 
 @SuppressWarnings("unused")
 public class AdHelper {
-    public static void initializeAds(View view, Context applicationContext, FragmentManager fragmentManager, String googleAppId, String adUnitId) {
+    public static void initializeAds(View view, Context applicationContext, Activity activity, FragmentManager fragmentManager, String adUnitId) {
         // Do nothing because this is the standard flavor.
     }
 
-    public static void loadAd(View view, Context applicationContext, String adUnitId) {
+    public static void loadAd(View view, Context applicationContext, Activity activity, String adUnitId) {
         // Do nothing because this is the standard flavor.
     }
 
index 2ce9c935d9cdec80b12bdd951ac2c148790eaaa3..d01688b0d2213926cca0cba19b6863be729ce73c 100644 (file)
@@ -25,7 +25,7 @@ buildscript {
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.1'
+        classpath 'com.android.tools.build:gradle:4.1.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21"
 
         // NOTE: Do not place your application dependencies here; they belong