]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
Increase the delay time that the SSL certificate dialog won't display after restart...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.kt
index d586dddd77d6cc195468b122a5d2010e553b8767..de608492aade9b22fe3e09a62d0b1990155dc7df 100644 (file)
@@ -53,7 +53,6 @@ import android.provider.OpenableColumns
 import android.text.Editable
 import android.text.TextWatcher
 import android.text.style.ForegroundColorSpan
-import android.util.Log
 import android.util.Patterns
 import android.util.TypedValue
 import android.view.ContextMenu
@@ -105,6 +104,8 @@ import androidx.cursoradapter.widget.CursorAdapter
 import androidx.drawerlayout.widget.DrawerLayout
 import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
 import androidx.viewpager2.widget.ViewPager2
 import androidx.webkit.WebSettingsCompat
@@ -281,6 +282,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
     private lateinit var navigationForwardMenuItem: MenuItem
     private lateinit var navigationHistoryMenuItem: MenuItem
     private lateinit var navigationRequestsMenuItem: MenuItem
+    private lateinit var navigationView: NavigationView
     private lateinit var optionsAddOrEditDomainMenuItem: MenuItem
     private lateinit var optionsBlockAllThirdPartyRequestsMenuItem: MenuItem
     private lateinit var optionsClearCookiesMenuItem: MenuItem
@@ -322,6 +324,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
     private lateinit var optionsUserAgentSafariOnIosMenuItem: MenuItem
     private lateinit var optionsUserAgentSafariOnMacosMenuItem: MenuItem
     private lateinit var optionsUserAgentWebViewDefaultMenuItem: MenuItem
+    private lateinit var optionsViewSourceMenuItem: MenuItem
     private lateinit var optionsWideViewportMenuItem: MenuItem
     private lateinit var proxyHelper: ProxyHelper
     private lateinit var redColorSpan: ForegroundColorSpan
@@ -375,11 +378,13 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
     private var inFullScreenBrowsingMode = false
     private var incognitoModeEnabled = false
     private var loadingNewIntent = false
+    private var navigationDrawerFirstView = true
     private var objectAnimator = ObjectAnimator()
     private var optionsMenu: Menu? = null
     private var orbotStatusBroadcastReceiver: BroadcastReceiver? = null
     private var reapplyAppSettingsOnRestart = false
     private var reapplyDomainSettingsOnRestart = false
+    private var restartTime = Date(0)
     private var sanitizeAmpRedirects = false
     private var sanitizeTrackingQueries = false
     private var savedProxyMode: String? = null
@@ -588,7 +593,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             tabLayout = findViewById(R.id.tablayout)
             swipeRefreshLayout = findViewById(R.id.swiperefreshlayout)
             webViewViewPager2 = findViewById(R.id.webview_viewpager2)
-            val navigationView = findViewById<NavigationView>(R.id.navigationview)
+            navigationView = findViewById(R.id.navigationview)
             bookmarksListView = findViewById(R.id.bookmarks_drawer_listview)
             bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview)
             bookmarksDrawerPinnedImageView = findViewById(R.id.bookmarks_drawer_pinned_imageview)
@@ -686,6 +691,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
 
                         // Go back.
                         currentWebView!!.goBack()
+
+                        // Update the URL edit text after a delay.
+                        updateUrlEditTextAfterDelay()
                     } else {  // Close the current tab.
                         // A view is required because the method is also called by an XML `onClick`.
                         closeTab(null)
@@ -1004,17 +1012,23 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         // Run the default commands.
         super.onConfigurationChanged(newConfig)
 
+        // Reset the navigation drawer first view flag.
+        navigationDrawerFirstView = true
+
         // Get the current page.
         val currentPage = webViewViewPager2.currentItem
 
         // Toggle the pages if there is more than one so that the view pager will recalculate their size.
         if (currentPage > 0) {
-            // Switch to the previous page.
-            webViewViewPager2.currentItem = (currentPage - 1)
+            // Switch to the previous page after 25 milliseconds.
+            webViewViewPager2.postDelayed ({ webViewViewPager2.currentItem = (currentPage - 1) }, 25)
 
-            // Switch back to the current page after the view pager has quiesced.
-            webViewViewPager2.post { webViewViewPager2.currentItem = currentPage }
+            // Switch back to the current page after the view pager has quiesced (which we are deciding should be 25 milliseconds).
+            webViewViewPager2.postDelayed ({ webViewViewPager2.currentItem = currentPage }, 25)
         }
+
+        // Scroll to the current tab position after 25 milliseconds.
+        tabLayout.postDelayed ({ tabLayout.setScrollPosition(currentPage, 0F, false, false) }, 25)
     }
 
     override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -1064,6 +1078,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images)
         optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview)
         optionsFontSizeMenuItem = menu.findItem(R.id.font_size)
+        optionsViewSourceMenuItem = menu.findItem(R.id.view_source)
         optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain)
 
         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
@@ -1138,7 +1153,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             optionsWideViewportMenuItem.isChecked = currentWebView!!.settings.useWideViewPort
             optionsDisplayImagesMenuItem.isChecked = currentWebView!!.settings.loadsImagesAutomatically
 
-            // Initialize the display names for the filter lists with the number of blocked requests.
+            // Set the display names for the filter lists with the number of blocked requests.
             optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + currentWebView!!.getRequestsCount(BLOCKED_REQUESTS)
             optionsEasyListMenuItem.title = currentWebView!!.getRequestsCount(EASYLIST).toString() + " - " + getString(R.string.easylist)
             optionsEasyPrivacyMenuItem.title = currentWebView!!.getRequestsCount(EASYPRIVACY).toString() + " - " + getString(R.string.easyprivacy)
@@ -1160,6 +1175,12 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             // Set the checkbox status for dark WebView if algorithmic darkening is supported.
             if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING))
                 optionsDarkWebViewMenuItem.isChecked = WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView!!.settings)
+
+            // Set the view source title according to the current URL.
+            if (currentWebView!!.currentUrl.startsWith("view-source:"))
+                optionsViewSourceMenuItem.title = getString(R.string.view_rendered_website)
+            else
+                optionsViewSourceMenuItem.title = getString(R.string.view_source)
         }
 
         // Set the cookies menu item checked status.
@@ -2004,15 +2025,29 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             }
 
             R.id.view_source -> {  // View source.
-                // Create an intent to launch the view source activity.
-                val viewSourceIntent = Intent(this, ViewSourceActivity::class.java)
+                // Open a new tab according to the current URL.
+                if (currentWebView!!.currentUrl.startsWith("view-source:")) {  // The source is currently viewed.
+                    // Open the rendered website in a new tab.
+                    addNewTab(currentWebView!!.currentUrl.substring(12, currentWebView!!.currentUrl.length), true)
+                } else {  // The rendered website is currently viewed.
+                    // Open the source in a new tab.
+                    addNewTab("view-source:${currentWebView!!.currentUrl}", true)
+                }
+
+                // Consume the event.
+                true
+            }
+
+            R.id.view_headers -> {  // View headers.
+                // Create an intent to launch the view headers activity.
+                val viewHeadersIntent = Intent(this, ViewHeadersActivity::class.java)
 
                 // Add the variables to the intent.
-                viewSourceIntent.putExtra(CURRENT_URL, currentWebView!!.url)
-                viewSourceIntent.putExtra(USER_AGENT, currentWebView!!.settings.userAgentString)
+                viewHeadersIntent.putExtra(CURRENT_URL, currentWebView!!.url)
+                viewHeadersIntent.putExtra(USER_AGENT, currentWebView!!.settings.userAgentString)
 
                 // Make it so.
-                startActivity(viewSourceIntent)
+                startActivity(viewHeadersIntent)
 
                 // Consume the event.
                 true
@@ -2210,6 +2245,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
 
                     // Load the previous website in the history.
                     currentWebView!!.goBack()
+
+                    // Update the URL edit text after a delay.
+                    updateUrlEditTextAfterDelay()
                 }
             }
 
@@ -2227,6 +2265,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
 
                     // Load the next website in the history.
                     currentWebView!!.goForward()
+
+                    // Update the URL edit text after a delay.
+                    updateUrlEditTextAfterDelay()
                 }
             }
 
@@ -2762,8 +2803,11 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         // Set a custom view on the new tab.
         newTab.setCustomView(R.layout.tab_custom_view)
 
+        // Scroll to the new tab position.
+        tabLayout.post { tabLayout.setScrollPosition(newTabNumber, 0F, false, false) }
+
         // Add the new WebView page.
-        webViewStateAdapter!!.addPage(newTabNumber, webViewViewPager2, urlString, moveToTab)
+        webViewStateAdapter!!.addPage(newTabNumber, newTab, urlString, moveToTab)
 
         // Show the app bar if it is at the bottom of the screen and the new tab is taking focus.
         if (bottomAppBar && moveToTab && appBarLayout.translationY != 0f) {
@@ -3395,6 +3439,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         if (reloadWebsite)
             nestedScrollWebView.reload()
 
+        // Disable the wide viewport if the source is being viewed.
+        if (url.startsWith("view-source:"))
+            nestedScrollWebView.settings.useWideViewPort = false
+
         // Load the URL if directed.  This makes sure that the domain settings are properly loaded before the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
         if (loadUrl)
             nestedScrollWebView.loadUrl(url)
@@ -3958,7 +4006,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         if ((savedStateArrayList == null) || (savedStateArrayList!!.size == 0)) {  // The activity has not been restarted or it was restarted on start to change the theme.
             // Add the first tab.
             addNewTab("", false)
-        } else {  // The activity has been restarted.
+        } else {  // The activity has been restarted with a saved state.
+            // Set the current restart time.
+            restartTime = Date()
+
             // Restore each tab.
             for (i in savedStateArrayList!!.indices) {
                 // Add a new tab.
@@ -3978,19 +4029,19 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             savedStateArrayList = null
             savedNestedScrollWebViewStateArrayList = null
 
-            Log.i("Tab", "Saved tab position:  $savedTabPosition")
-
             // Restore the selected tab position.
             if (savedTabPosition == 0) {  // The first tab is selected.
                 // Set the first page as the current WebView.
                 setCurrentWebView(0)
             } else {  // The first tab is not selected.
-                // Switch to the page before the saved tab position.
-                webViewViewPager2.post { webViewViewPager2.currentItem = (savedTabPosition - 1) }
+                // Select the tab when the layout has finished populating.
+                tabLayout.post {
+                    // Get a handle for the tab.
+                    val tab = tabLayout.getTabAt(savedTabPosition)!!
 
-                // Switch to the saved tab position.
-                // This has to be done twice because, for some reason, if the above step is skipped there is some race condition where nothing happens and the first page is displayed.
-                webViewViewPager2.post { webViewViewPager2.currentItem = savedTabPosition }
+                    // Select the tab.
+                    tab.select()
+                }
             }
 
             // Get the intent that started the app.
@@ -4150,44 +4201,37 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
         val createBookmarkFab = findViewById<FloatingActionButton>(R.id.create_bookmark_fab)
 
-        // Update the WebView pager every time a tab is modified.
-        webViewViewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
-            override fun onPageSelected(position: Int) {
-                // Close the find on page bar if it is open.
-                closeFindOnPage(null)
-
-                // Set the current WebView.
-                setCurrentWebView(position)
-
-                // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled by creating a new tab.
-                if (tabLayout.selectedTabPosition != position) {
-                    // Wait until the new tab has been created.
-                    tabLayout.post {
-                        // Get a handle for the tab.
-                        val tab = tabLayout.getTabAt(position)!!
-
-                        // Select the tab.
-                        tab.select()
-                    }
-                }
-            }
-        })
-
         // Handle tab selections.
         tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
             override fun onTabSelected(tab: TabLayout.Tab) {
+                // Close the find on page bar if it is open.
+                closeFindOnPage(null)
+
                 // Select the same page in the view pager.
                 webViewViewPager2.currentItem = tab.position
+
+                // Set the current WebView.
+                setCurrentWebView(tab.position)
             }
 
             override fun onTabUnselected(tab: TabLayout.Tab) {}
 
             override fun onTabReselected(tab: TabLayout.Tab) {
-                // Instantiate the View SSL Certificate dialog.
-                val viewSslCertificateDialogFragment: DialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView!!.webViewFragmentId, currentWebView!!.getFavoriteIcon())
-
-                // Display the View SSL Certificate dialog.
-                viewSslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.view_ssl_certificate))
+                // Only display the view SSL certificate dialog if the current WebView is not null.
+                // This can happen if the tab is programmatically reselected while the app is being restarted and is not yet populated.
+                if (currentWebView != null) {
+                    // Calculate the milliseconds since the last restart.  This can be replaced by the simpler LocalDateTime once the minimum API >= 26.
+                    val millisecondsSinceLastRestart = Date().time - restartTime.time
+
+                    // Only display the SSL certificate dialog if it has been at least 2 seconds since the last restart as deep restarts sometimes end up selecting a tab twice.
+                    if (millisecondsSinceLastRestart > 2000) {
+                        // Instantiate the View SSL Certificate dialog.
+                        val viewSslCertificateDialogFragment: DialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView!!.webViewFragmentId, currentWebView!!.getFavoriteIcon())
+
+                        // Display the View SSL Certificate dialog.
+                        viewSslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.view_ssl_certificate))
+                    }
+                }
             }
         })
 
@@ -4407,6 +4451,20 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     // Clear the focus from from the WebView if it is not null, which can happen if a user opens a drawer while the browser is being resumed.
                     // Clearing the focus from the WebView removes any text selection markers and context menus, which otherwise draw above the open drawers.
                     currentWebView?.clearFocus()
+
+                    if (bottomAppBar && navigationDrawerFirstView) {
+                        // Reset the navigation drawer first view flag.
+                        navigationDrawerFirstView = false
+
+                        // Get a handle for the navigation recycler view.
+                        val navigationRecyclerView = navigationView.getChildAt(0) as RecyclerView
+
+                        // Get the navigation linear layout manager.
+                        val navigationLinearLayoutManager = navigationRecyclerView.layoutManager as LinearLayoutManager
+
+                        // Scroll the navigation drawer to the bottom.
+                        navigationLinearLayoutManager.scrollToPositionWithOffset(13, 0)
+                    }
                 }
             }
         })
@@ -5349,8 +5407,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
 
                 // Update the URL text bar if the page is currently selected and the URL edit text is not currently being edited.
                 if ((tabLayout.selectedTabPosition == currentPagePosition) && !urlEditText.hasFocus()) {
-                    // Display the formatted URL text.
-                    urlEditText.setText(url)
+                    // Display the formatted URL text.  The nested scroll WebView current URL preserves any initial `view-source:`, and opposed to the method URL variable.
+                    urlEditText.setText(nestedScrollWebView.currentUrl)
 
                     // Highlight the URL syntax.
                     UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
@@ -5692,7 +5750,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         var urlString = ""
 
         // Check to see if the unformatted URL string is a valid URL.  Otherwise, convert it into a search.
-        if (unformattedUrlString.startsWith("content://")) {  // This is a content URL.
+        if (unformattedUrlString.startsWith("content://") || unformattedUrlString.startsWith("view-source:")) {  // This is a content or source URL.
             // Load the entire content URL.
             urlString = unformattedUrlString
         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
@@ -5756,6 +5814,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
 
         // Load the history entry.
         currentWebView!!.goBackOrForward(steps)
+
+        // Update the URL edit text after a delay.
+        updateUrlEditTextAfterDelay()
     }
 
     override fun openFile(dialogFragment: DialogFragment) {
@@ -5866,6 +5927,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
 
         // Go back.
         currentWebView!!.goBack()
+
+        // Update the URL edit text after a delay.
+        updateUrlEditTextAfterDelay()
     }
 
     private fun sanitizeUrl(urlString: String): String {
@@ -5908,16 +5972,16 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         // Stop the swipe to refresh indicator if it is running
         swipeRefreshLayout.isRefreshing = false
 
-        // Get the WebView tab fragment.
-        val webViewTabFragment = webViewStateAdapter!!.getPageFragment(pageNumber)
+        // Try to set the current WebView.  This will fail if the WebView has not yet been populated.
+        try {
+            // Get the WebView tab fragment.
+            val webViewTabFragment = webViewStateAdapter!!.getPageFragment(pageNumber)
 
-        // Get the fragment view.
-        val webViewFragmentView = webViewTabFragment.view
+            // Get the fragment view.
+            val webViewFragmentView = webViewTabFragment.view
 
-        // Set the current WebView if the fragment view is not null.
-        if (webViewFragmentView != null) {  // The fragment has been populated.
             // Store the current WebView.
-            currentWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview)
+            currentWebView = webViewFragmentView!!.findViewById(R.id.nestedscroll_webview)
 
             // Update the status of swipe to refresh.
             if (currentWebView!!.swipeToRefresh) {  // Swipe to refresh is enabled.
@@ -5977,8 +6041,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 // Remove any background on the URL relative layout.
                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.color.transparent)
             }
-        } else if ((pageNumber == savedTabPosition) || (pageNumber >= (webViewStateAdapter!!.itemCount - 1))) {  // The tab has not been populated yet.
-            //  Try again in 100 milliseconds if the app is being restored or the a new tab has been added (the last tab).
+        }  catch (exception: Exception) {
+            //  Try again in 100 milliseconds if the WebView has not yet been populated.
             // Create a handler to set the current WebView.
             val setCurrentWebViewHandler = Handler(Looper.getMainLooper())
 
@@ -5988,8 +6052,8 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 setCurrentWebView(pageNumber)
             }
 
-            // Try setting the current WebView again after 100 milliseconds.
-            setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100)
+            // Try setting the current WebView again after 50 milliseconds.
+            setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 50)
         }
     }
 
@@ -6085,4 +6149,22 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 invalidateOptionsMenu()
         }
     }
+
+    fun updateUrlEditTextAfterDelay() {
+        // Create a handler to update the URL edit box.
+        val urlEditTextUpdateHandler = Handler(Looper.getMainLooper())
+
+        // Create a runnable to update the URL edit box.
+        val urlEditTextUpdateRunnable = Runnable {
+            // Update the URL edit text.
+            urlEditText.setText(currentWebView!!.url)
+
+            // Disable the wide viewport if the source is being viewed.
+            if (currentWebView!!.url!!.startsWith("view-source:"))
+                currentWebView!!.settings.useWideViewPort = false
+        }
+
+        // Update the URL edit text after 50 milliseconds, so that the WebView has enough time to navigate to the new URL.
+        urlEditTextUpdateHandler.postDelayed(urlEditTextUpdateRunnable, 50)
+    }
 }