import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.cursoradapter.widget.CursorAdapter
import androidx.drawerlayout.widget.DrawerLayout
private lateinit var navigationForwardMenuItem: MenuItem
private lateinit var navigationHistoryMenuItem: MenuItem
private lateinit var navigationRequestsMenuItem: MenuItem
+ private lateinit var navigationScrollToBottomMenuItem: MenuItem
private lateinit var navigationView: NavigationView
private lateinit var optionsAddOrEditDomainMenuItem: MenuItem
private lateinit var optionsBlockAllThirdPartyRequestsMenuItem: MenuItem
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
// Get handles for the navigation menu items.
navigationBackMenuItem = navigationMenu.findItem(R.id.back)
navigationForwardMenuItem = navigationMenu.findItem(R.id.forward)
+ navigationScrollToBottomMenuItem = navigationMenu.findItem(R.id.scroll_to_bottom)
navigationHistoryMenuItem = navigationMenu.findItem(R.id.history)
navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests)
// Run the default commands.
super.onNewIntent(intent)
+ // Close the navigation drawer if it is open.
+ if (drawerLayout.isDrawerVisible(GravityCompat.START))
+ drawerLayout.closeDrawer(GravityCompat.START)
+
+ // Close the bookmarks drawer if it is open.
+ if (drawerLayout.isDrawerVisible(GravityCompat.END))
+ drawerLayout.closeDrawer(GravityCompat.END)
+
// Get the information from the intent.
val intentAction = intent.action
val intentUriData = intent.data
// Make it so.
loadUrl(currentWebView!!, url!!)
}
-
- // Close the navigation drawer if it is open.
- if (drawerLayout.isDrawerVisible(GravityCompat.START))
- drawerLayout.closeDrawer(GravityCompat.START)
-
- // Close the bookmarks drawer if it is open.
- if (drawerLayout.isDrawerVisible(GravityCompat.END))
- drawerLayout.closeDrawer(GravityCompat.END)
}
} else { // The app has been restarted.
- // If the new intent will open a new tab, set the saved tab position to be the size of the saved state array list.
- // The tab position is 0 based, meaning the new tab will be the tab position that is restored.
- if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch)
- savedTabPosition = savedStateArrayList!!.size
-
// Replace the intent that started the app with this one. This will load the tab after the others have been restored.
setIntent(intent)
}
}
}
+ R.id.scroll_to_bottom -> { // Scroll to Bottom.
+ // Check if the WebView is scrolled to the top.
+ if (currentWebView!!.scrollY == 0) { // The WebView is at the top; scroll to the bottom. Using a large Y number is more efficient than trying to calculate the exact WebView length.
+ currentWebView!!.scrollTo(0, 1_000_000_000)
+ } else { // The WebView is not at the top; scroll to the top.
+ currentWebView!!.scrollTo(0, 0)
+ }
+ }
+
R.id.history -> { // History.
// Instantiate the URL history dialog.
val urlHistoryDialogFragment: DialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView!!.webViewFragmentId)
true
}
+ // Add a Share URL entry.
+ contextMenu.add(R.string.share_url).setOnMenuItemClickListener {
+ // Create the share intent.
+ val shareUrlIntent = Intent(Intent.ACTION_SEND)
+
+ // Add the URL to the intent.
+ shareUrlIntent.putExtra(Intent.EXTRA_TEXT, linkUrl)
+
+ // Set the MIME type.
+ shareUrlIntent.type = "text/plain"
+
+ // Set the intent to open in a new task.
+ shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ //Make it so.
+ startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
+
+ // Consume the event.
+ true
+ }
+
// Add an empty cancel entry, which by default closes the context menu.
contextMenu.add(R.string.cancel)
}
true
}
+ // Add a Share URL entry.
+ contextMenu.add(R.string.share_url).setOnMenuItemClickListener {
+ // Create the share intent.
+ val shareUrlIntent = Intent(Intent.ACTION_SEND)
+
+ // Add the URL to the intent.
+ shareUrlIntent.putExtra(Intent.EXTRA_TEXT, imageUrl)
+
+ // Set the MIME type.
+ shareUrlIntent.type = "text/plain"
+
+ // Set the intent to open in a new task.
+ shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ //Make it so.
+ startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
+
+ // Consume the event.
+ true
+ }
+
// Add an empty cancel entry, which by default closes the context menu.
contextMenu.add(R.string.cancel)
}
true
}
+ // Add a Share Image entry.
+ contextMenu.add(R.string.share_image).setOnMenuItemClickListener {
+ // Create the share intent.
+ val shareUrlIntent = Intent(Intent.ACTION_SEND)
+
+ // Add the URL to the intent.
+ shareUrlIntent.putExtra(Intent.EXTRA_TEXT, imageUrl)
+
+ // Set the MIME type.
+ shareUrlIntent.type = "text/plain"
+
+ // Set the intent to open in a new task.
+ shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ //Make it so.
+ startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
+
+ // Consume the event.
+ true
+ }
+
// Add a copy URL entry.
contextMenu.add(R.string.copy_url).setOnMenuItemClickListener {
// Save the link URL in a clip data.
true
}
+ // Add a Share URL entry.
+ contextMenu.add(R.string.share_url).setOnMenuItemClickListener {
+ // Create the share intent.
+ val shareUrlIntent = Intent(Intent.ACTION_SEND)
+
+ // Add the URL to the intent.
+ shareUrlIntent.putExtra(Intent.EXTRA_TEXT, linkUrl)
+
+ // Set the MIME type.
+ shareUrlIntent.type = "text/plain"
+
+ // Set the intent to open in a new task.
+ shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ //Make it so.
+ startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
+
+ // Consume the event.
+ true
+ }
+
// Add an empty cancel entry, which by default closes the context menu.
contextMenu.add(R.string.cancel)
}
// Get the package manager.
val packageManager = packageManager
- // Check to see if Orbot is in the list. This will throw an error and drop to the catch section if it isn't installed. The deprecated method must be used until the minimum API >= 33.
- @Suppress("DEPRECATION")
+ // Check to see if Orbot is in the list. This will throw an error and drop to the catch section if it isn't installed.
packageManager.getPackageInfo("org.torproject.android", 0)
// Check to see if the proxy is ready.
// Check to see if I2P is installed.
try {
// Check to see if the F-Droid flavor is installed. This will throw an error and drop to the catch section if it isn't installed.
- // The deprecated method must be used until the minimum API >= 33.
- @Suppress("DEPRECATION")
packageManager.getPackageInfo("net.i2p.android.router", 0)
} catch (fdroidException: PackageManager.NameNotFoundException) { // The F-Droid flavor is not installed.
try {
// Check to see if the Google Play flavor is installed. This will throw an error and drop to the catch section if it isn't installed.
- // The deprecated method must be used until the minimum API >= 33.
- @Suppress("DEPRECATION")
packageManager.getPackageInfo("net.i2p.android", 0)
} catch (googlePlayException: PackageManager.NameNotFoundException) { // The Google Play flavor is not installed.
// Sow the I2P not installed dialog if it is not already displayed.
// Add the first tab.
addNewTab("", false)
} 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.
savedStateArrayList = null
savedNestedScrollWebViewStateArrayList = null
- // 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.
- // Select the tab when the layout has finished populating.
- tabLayout.post {
- // Get a handle for the tab.
- val tab = tabLayout.getTabAt(savedTabPosition)!!
-
- // Select the tab.
- tab.select()
- }
- }
-
// Get the intent that started the app.
val intent = intent
val isWebSearch = (intentAction != null) && (intentAction == Intent.ACTION_WEB_SEARCH)
// Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null.
- if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch) {
+ if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch) { // A new tab is being loaded.
// Get the URL string.
val urlString = if (isWebSearch) { // The intent is a web search.
// Sanitize the search input.
// Make it so.
loadUrl(currentWebView!!, urlString)
}
+ } else { // A new tab is not being loaded.
+ // 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.
+ // Select the tab when the layout has finished populating.
+ tabLayout.post {
+ // Get a handle for the tab.
+ val tab = tabLayout.getTabAt(savedTabPosition)!!
+
+ // Select the tab.
+ tab.select()
+ }
+ }
}
}
}
}
}
- // Register the Orbot status broadcast receiver.
- registerReceiver(orbotStatusBroadcastReceiver, IntentFilter("org.torproject.android.intent.action.STATUS"))
+ // Register the Orbot status broadcast receiver. `ContextCompat` must be used until the minimum API >= 34.
+ ContextCompat.registerReceiver(this, orbotStatusBroadcastReceiver, IntentFilter("org.torproject.android.intent.action.STATUS"), ContextCompat.RECEIVER_EXPORTED)
// Get handles for views that need to be modified.
val bookmarksHeaderLinearLayout = findViewById<LinearLayout>(R.id.bookmarks_header_linearlayout)
// Close the find on page bar if it is open.
closeFindOnPage(null)
- // Select the same page in the view pager.
- webViewViewPager2.currentItem = tab.position
+ // Update the view pager when it has quiesced. Otherwise, if a page launched by a new intent on restart has not yet been created, the view pager will not be updated to match the tab layout.
+ webViewViewPager2.post {
+ // Select the same page in the view pager.
+ webViewViewPager2.currentItem = tab.position
- // Set the current WebView.
- setCurrentWebView(tab.position)
+ // Set the current WebView.
+ setCurrentWebView(tab.position)
+ }
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
// 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())
+ // 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))
- }
+ // Display the View SSL Certificate dialog.
+ viewSslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.view_ssl_certificate))
}
}
})
if (newState == DrawerLayout.STATE_SETTLING || newState == DrawerLayout.STATE_DRAGGING) { // A drawer is opening or closing.
// Update the navigation menu items if the WebView is not null.
if (currentWebView != null) {
+ // Set the enabled status of the menu items.
navigationBackMenuItem.isEnabled = currentWebView!!.canGoBack()
navigationForwardMenuItem.isEnabled = currentWebView!!.canGoForward()
+ navigationScrollToBottomMenuItem.isEnabled = (currentWebView!!.canScrollVertically(-1) || currentWebView!!.canScrollVertically(1))
navigationHistoryMenuItem.isEnabled = currentWebView!!.canGoBack() || currentWebView!!.canGoForward()
+
+ // Update the scroll menu item.
+ if (currentWebView!!.scrollY == 0) { // The WebView is scrolled to the top.
+ // Set the title.
+ navigationScrollToBottomMenuItem.title = getString(R.string.scroll_to_bottom)
+
+ // Set the icon.
+ navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_down_enabled)
+ } else { // The WebView is not scrolled to the top.
+ // Set the title.
+ navigationScrollToBottomMenuItem.title = getString(R.string.scroll_to_top)
+
+ // Set the icon.
+ navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_up_enabled)
+ }
+
+ // Display the number of blocked requests.
navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + currentWebView!!.getRequestsCount(BLOCKED_REQUESTS)
// Hide the keyboard (if displayed).
}
}
- override fun onFling(motionEvent1: MotionEvent, motionEvent2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
+ override fun onFling(motionEvent1: MotionEvent?, motionEvent2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
// Scroll the bottom app bar if enabled.
- if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning) {
+ if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning && (motionEvent1 != null)) {
// Calculate the Y change.
val motionY = motionEvent2.y - motionEvent1.y
} else { // Load the URL.
loadUrl(nestedScrollWebView, urlToLoadString!!)
}
-
- // Reset the intent. This prevents a duplicate tab from being created on a subsequent restart if loading an link from a new intent on restart.
- // For example, this prevents a duplicate tab if a link is loaded from the Guide after changing the theme in the guide and then changing the theme again in the main activity.
- intent = Intent()
} else { // This is not the first tab.
// Load the URL.
loadUrl(nestedScrollWebView, urlString)