X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=3d92e076e370a669d49b927146abe662f56c3d8e;hp=d3f0d016b281676db475fdff9d8035b669e39649;hb=6a85cc5ca039054a24c4601de785e0bc1a234722;hpb=b4fc354bee486a2df17b3aae6760a3c61eba1e97 diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index d3f0d016..3d92e076 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -26,6 +26,7 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.app.DialogFragment; import android.app.DownloadManager; +import android.app.SearchManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ClipData; @@ -36,6 +37,7 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -292,7 +294,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private CoordinatorLayout rootCoordinatorLayout; // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, - // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `setDisplayWebpageImages()`, and `applyProxyThroughOrbot()`. + // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`. private WebView mainWebView; // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`. @@ -331,9 +333,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. private boolean nightMode; - // `displayWebpageImagesBoolean` is used in `applyAppSettings()` and `applyDomainSettings()`. - private boolean displayWebpageImagesBoolean; - // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`. private String homepage; @@ -398,6 +397,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`. private boolean displayingFullScreenVideo; + // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`. + private boolean downloadWithExternalApp; + // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`. private String currentDomainName; @@ -410,18 +412,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`. private boolean waitingForOrbot; - // `domainSettingsApplied` is used in `prepareOptionsMenu()`, `applyDomainSettings()`, and `setDisplayWebpageImages()`. + // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`. private boolean domainSettingsApplied; // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`. private Boolean domainSettingsJavaScriptEnabled; - // `displayWebpageImagesInt` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`. - private int displayWebpageImagesInt; - - // `onTheFlyDisplayImagesSet` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`. - private boolean onTheFlyDisplayImagesSet; - // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`. private String waitingForOrbotHtmlString; @@ -511,7 +507,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers. // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`. @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"}) - // Remove Android Studio's warning about deprecations. We have to use the deprecated `getColor()` until API >= 23. + // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23. @SuppressWarnings("deprecation") protected void onCreate(Bundle savedInstanceState) { // Get a handle for the shared preferences. @@ -539,6 +535,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the content view. setContentView(R.layout.main_drawerlayout); + // Get a handle for the resources. + Resources resources = getResources(); + // Get a handle for `inputMethodManager`. inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); @@ -555,9 +554,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23. - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); - initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); - finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); + redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700)); + initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500)); + finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500)); // Get a handle for `urlTextBox`. urlTextBox = findViewById(R.id.url_edittext); @@ -583,11 +582,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // If the event is a key-down event on the `enter` button, load the URL. if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // Load the URL into the mainWebView and consume the event. - try { - loadUrlFromTextBox(); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } + loadUrlFromTextBox(); + // If the enter key was pressed, consume the event. return true; } else { @@ -643,15 +639,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget. if (darkTheme) { - launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark)); - createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark)); - createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark)); - bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850)); + launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark)); + createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark)); + createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark)); + bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850)); } else { - launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light)); - createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light)); - createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light)); - bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white)); + launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light)); + createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light)); + createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light)); + bookmarksListView.setBackgroundColor(resources.getColor(R.color.white)); } // Set the launch bookmarks activity FAB to launch the bookmarks activity. @@ -670,14 +666,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook createBookmarkFolderFab.setOnClickListener(v -> { // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`. AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog(); - createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder)); + createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder)); }); // Set the create new bookmark FAB to display an alert dialog. createBookmarkFab.setOnClickListener(view -> { // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`. AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog(); - createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark)); + createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark)); }); // Create a double-tap listener to toggle full-screen mode. @@ -821,11 +817,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); - // Implement swipe to refresh - swipeRefreshLayout = findViewById(R.id.swipe_refreshlayout); - swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); + // Implement swipe to refresh. + swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload()); + // Set the swipe to refresh color according to the theme. + if (darkTheme) { + swipeRefreshLayout.setColorSchemeResources(R.color.blue_600); + swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850); + } else { + swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); + } + // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode. drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer)); drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks)); @@ -891,17 +894,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`. AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId); - editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder)); + editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder)); } else { // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`. AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId); - editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark)); + editBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_bookmark)); } // Consume the event. return true; }); + // Get the status bar pixel size. + int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); + int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId); + + // Get the resource density. + float screenDensity = resources.getDisplayMetrics().density; + + // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts. + int drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity); + int drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity); + int drawerHeaderPaddingBottom = (int) (8 * screenDensity); + // The drawer listener is used to update the navigation menu. drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { @Override @@ -919,6 +934,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onDrawerStateChanged(int newState) { if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing. + // Get handles for the drawer headers. + TextView navigationHeaderTextView = findViewById(R.id.navigationText); + TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview); + + // Apply the calculated drawer paddings. This moves the text in the header below any cutouts. + navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom); + bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom); + // Update the back, forward, history, and requests menu items. navigationBackMenuItem.setEnabled(mainWebView.canGoBack()); navigationForwardMenuItem.setEnabled(mainWebView.canGoForward()); @@ -928,8 +951,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Hide the keyboard (if displayed). inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); - // Clear the focus from from the URL text box. + // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers. urlTextBox.clearFocus(); + mainWebView.clearFocus(); } } }); @@ -1147,32 +1171,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Allow the downloading of files. mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> { - // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - // The WRITE_EXTERNAL_STORAGE permission needs to be requested. - - // Store the variables for future use by `onRequestPermissionsResult()`. - downloadUrl = url; - downloadContentDisposition = contentDisposition; - downloadContentLength = contentLength; - - // Show a dialog if the user has previously denied the permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE. - DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE); - - // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed. - downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); - } else { // Show the permission request directly. - // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); - } - } else { // The storage permission has already been granted. - // Get a handle for the download file alert dialog. - AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength); + // Check if the download should be processed by an external app. + if (downloadWithExternalApp) { // Download with an external app. + openUrlWithExternalApp(url); + } else { // Download with Android's download manager. + // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted. + // The WRITE_EXTERNAL_STORAGE permission needs to be requested. + + // Store the variables for future use by `onRequestPermissionsResult()`. + downloadUrl = url; + downloadContentDisposition = contentDisposition; + downloadContentLength = contentLength; + + // Show a dialog if the user has previously denied the permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE. + DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE); + + // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed. + downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); + } else { // Show the permission request directly. + // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); + } + } else { // The storage permission has already been granted. + // Get a handle for the download file alert dialog. + AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength); - // Show the download file alert dialog. - downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + // Show the download file alert dialog. + downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } } }); @@ -1200,17 +1229,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default. PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - // Get the intent that started the app. - final Intent launchingIntent = getIntent(); - - // Extract the launching intent data as `launchingIntentUriData`. - final Uri launchingIntentUriData = launchingIntent.getData(); - - // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`. - if (launchingIntentUriData != null) { - formattedUrlString = launchingIntentUriData.toString(); - } - // Get a handle for the `Runtime`. privacyBrowserRuntime = Runtime.getRuntime(); @@ -1248,7 +1266,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Initialize the user agent array adapter and string array. userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item); - userAgentDataArray = getResources().getStringArray(R.array.user_agent_data); + userAgentDataArray = resources.getStringArray(R.array.user_agent_data); // Apply the app settings from the shared preferences. applyAppSettings(); @@ -1287,10 +1305,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook formattedUrlString = ""; // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed. - applyDomainSettings(url, true, false); + boolean userAgentChanged = applyDomainSettings(url, true, false); - // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list. - return false; + // Check if the user agent has changed. + if (userAgentChanged) { + // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL. + mainWebView.loadUrl(url, customHeaders); + + // Returning true indicates that Privacy Browser is manually handling the loading of the URL. + return true; + } else { + // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list. + return false; + } } else if (url.startsWith("mailto:")) { // Load the email address in an external email program. // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. Intent emailIntent = new Intent(Intent.ACTION_SENDTO); @@ -1585,11 +1612,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Apply any custom domain settings if the URL was loaded by navigating history. if (navigatingHistory) { + // Apply the domain settings. + boolean userAgentChanged = applyDomainSettings(url, true, false); + // Reset `navigatingHistory`. navigatingHistory = false; - // Apply the domain settings. - applyDomainSettings(url, true, false); + // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded. + if (userAgentChanged) { + loadUrl(formattedUrlString); + } } // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page. @@ -1617,7 +1649,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public void onPageFinished(WebView view, String url) { // Reset the wide view port if it has been turned off by the waiting for Orbot message. if (!waitingForOrbot) { - mainWebView.getSettings().setUseWideViewPort(true); + // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`. + mainWebView.getSettings().setUseWideViewPort(url.startsWith("http")); } // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions. @@ -1699,7 +1732,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook sslCertificate = mainWebView.getCertificate(); // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session. - if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate) { + // Also ignore if changes in the user agent causes an error while navigating history. + if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) { // Initialize the current SSL certificate variables. String currentWebsiteIssuedToCName = ""; String currentWebsiteIssuedToOName = ""; @@ -1796,6 +1830,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); + // Get the intent that started the app. + Intent launchingIntent = getIntent(); + + // Get the information from the intent. + String launchingIntentAction = launchingIntent.getAction(); + Uri launchingIntentUriData = launchingIntent.getData(); + + // If the intent action is a web search, perform the search. + if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { + // Create an encoded URL string. + String encodedUrlString; + + // Sanitize the search input and convert it to a search. + try { + encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8"); + } catch (UnsupportedEncodingException exception) { + encodedUrlString = ""; + } + + // Add the base search URL. + formattedUrlString = searchURL + encodedUrlString; + } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL. + // Set the formatted URL string. + formattedUrlString = launchingIntentUriData.toString(); + } + // Load the website if not waiting for Orbot to connect. if (!waitingForOrbot) { loadUrl(formattedUrlString); @@ -1807,27 +1867,44 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity. setIntent(intent); - // Check to see if the intent contains a new URL. - if (intent.getData() != null) { - // Get the intent data. - final Uri intentUriData = intent.getData(); + // Get the information from the intent. + String intentAction = intent.getAction(); + Uri intentUriData = intent.getData(); - // Load the website. - loadUrl(intentUriData.toString()); + // If the intent action is a web search, perform the search. + if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) { + // Create an encoded URL string. + String encodedUrlString; - // Close the navigation drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { - drawerLayout.closeDrawer(GravityCompat.START); + // Sanitize the search input and convert it to a search. + try { + encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8"); + } catch (UnsupportedEncodingException exception) { + encodedUrlString = ""; } - // Close the bookmarks drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.END)) { - drawerLayout.closeDrawer(GravityCompat.END); - } + // Add the base search URL. + formattedUrlString = searchURL + encodedUrlString; + } else if (intentUriData != null){ // Check to see if the intent contains a new URL. + // Set the formatted URL string. + formattedUrlString = intentUriData.toString(); + } - // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it. - mainWebView.requestFocus(); + // Load the URL. + loadUrl(formattedUrlString); + + // 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); + } + + // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it. + mainWebView.requestFocus(); } @Override @@ -2725,9 +2802,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } else { // Images are not currently loaded automatically. mainWebView.getSettings().setLoadsImagesAutomatically(true); } - - // Set `onTheFlyDisplayImagesSet`. - onTheFlyDisplayImagesSet = true; return true; case R.id.night_mode: @@ -2746,7 +2820,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get the JavaScript preference. - javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false); + javaScriptEnabled = sharedPreferences.getBoolean("javascript", false); } // Apply the JavaScript setting to the WebView. @@ -2792,8 +2866,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String shareString = webViewTitle + " – " + urlTextBox.getText().toString(); // Create the share intent. - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); + Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_TEXT, shareString); shareIntent.setType("text/plain"); @@ -3145,32 +3218,35 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Add a Download URL entry. menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> { - // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - // The WRITE_EXTERNAL_STORAGE permission needs to be requested. - - // Store the variables for future use by `onRequestPermissionsResult()`. - downloadUrl = linkUrl; - downloadContentDisposition = "none"; - downloadContentLength = -1; - - // Show a dialog if the user has previously denied the permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE. - DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE); - - // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed. - downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); - } else { // Show the permission request directly. - // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); - } - } else { // The WRITE_EXTERNAL_STORAGE permission has already been granted. - // Get a handle for the download file alert dialog. - AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1); + // Check if the download should be processed by an external app. + if (downloadWithExternalApp) { // Download with an external app. + openUrlWithExternalApp(linkUrl); + } else { // Download with Android's download manager. + // Check to see if the storage permission has already been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested. + // Store the variables for future use by `onRequestPermissionsResult()`. + downloadUrl = linkUrl; + downloadContentDisposition = "none"; + downloadContentLength = -1; + + // Show a dialog if the user has previously denied the permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE. + DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE); + + // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed. + downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); + } else { // Show the permission request directly. + // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); + } + } else { // The storage permission has already been granted. + // Get a handle for the download file alert dialog. + AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1); - // Show the download file alert dialog. - downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + // Show the download file alert dialog. + downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } } return false; }); @@ -3188,7 +3264,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Add a `Write Email` entry. menu.add(R.string.write_email).setOnMenuItemClickListener(item -> { - // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. + // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. Intent emailIntent = new Intent(Intent.ACTION_SENDTO); // Parse the url and set it as the data for the `Intent`. @@ -3232,30 +3308,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Add a `Download Image` entry. menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { - // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - // The WRITE_EXTERNAL_STORAGE permission needs to be requested. - - // Store the image URL for use by `onRequestPermissionResult()`. - downloadImageUrl = imageUrl; - - // Show a dialog if the user has previously denied the permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. - DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); - - // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. - downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); - } else { // Show the permission request directly. - // Request the permission. The download dialog will be launched by `onRequestPermissionResult(). - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); - } - } else { // The WRITE_EXTERNAL_STORAGE permission has already been granted. - // Get a handle for the download image alert dialog. - AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); + // Check if the download should be processed by an external app. + if (downloadWithExternalApp) { // Download with an external app. + openUrlWithExternalApp(imageUrl); + } else { // Download with Android's download manager. + // Check to see if the storage permission has already been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested. + // Store the image URL for use by `onRequestPermissionResult()`. + downloadImageUrl = imageUrl; + + // Show a dialog if the user has previously denied the permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. + DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); + + // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. + downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); + } else { // Show the permission request directly. + // Request the permission. The download dialog will be launched by `onRequestPermissionResult(). + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); + } + } else { // The storage permission has already been granted. + // Get a handle for the download image alert dialog. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); - // Show the download image alert dialog. - downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + // Show the download image alert dialog. + downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } } return false; }); @@ -3291,30 +3370,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Add a `Download Image` entry. menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { - // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - // The WRITE_EXTERNAL_STORAGE permission needs to be requested. - - // Store the image URL for use by `onRequestPermissionResult()`. - downloadImageUrl = imageUrl; - - // Show a dialog if the user has previously denied the permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. - DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); - - // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. - downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); - } else { // Show the permission request directly. - // Request the permission. The download dialog will be launched by `onRequestPermissionResult(). - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); - } - } else { // The WRITE_EXTERNAL_STORAGE permission has already been granted. - // Get a handle for the download image alert dialog. - AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); + // Check if the download should be processed by an external app. + if (downloadWithExternalApp) { // Download with an external app. + openUrlWithExternalApp(imageUrl); + } else { // Download with Android's download manager. + // Check to see if the storage permission has already been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested. + // Store the image URL for use by `onRequestPermissionResult()`. + downloadImageUrl = imageUrl; + + // Show a dialog if the user has previously denied the permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. + DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); + + // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. + downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); + } else { // Show the permission request directly. + // Request the permission. The download dialog will be launched by `onRequestPermissionResult(). + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); + } + } else { // The storage permission has already been granted. + // Get a handle for the download image alert dialog. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); - // Show the download image alert dialog. - downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + // Show the download image alert dialog. + downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } } return false; }); @@ -3421,8 +3503,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap); // Setup the shortcut intent. - Intent shortcutIntent = new Intent(); - shortcutIntent.setAction(Intent.ACTION_VIEW); + Intent shortcutIntent = new Intent(Intent.ACTION_VIEW); shortcutIntent.setData(Uri.parse(formattedUrlString)); // Create a shortcut info builder. The shortcut name becomes the shortcut ID. @@ -3802,14 +3883,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - private void loadUrlFromTextBox() throws UnsupportedEncodingException { + private void loadUrlFromTextBox() { // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string. String unformattedUrlString = urlTextBox.getText().toString().trim(); // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search. - if ((Patterns.WEB_URL.matcher(unformattedUrlString).matches()) || (unformattedUrlString.startsWith("http://")) || (unformattedUrlString.startsWith("https://"))) { - // Add `https://` at the beginning if it is missing. Otherwise the app will segfault. - if (!unformattedUrlString.startsWith("http")) { + if (unformattedUrlString.startsWith("content://")) { + // Load the entire content URL. + formattedUrlString = unformattedUrlString; + } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") + || unformattedUrlString.startsWith("file://")) { + // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault. + if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) { unformattedUrlString = "https://" + unformattedUrlString; } @@ -3824,24 +3909,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value. - final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null; - final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null; - final String path = unformattedUrl != null ? unformattedUrl.getPath() : null; - final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null; - final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null; + String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null; + String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null; + String path = unformattedUrl != null ? unformattedUrl.getPath() : null; + String query = unformattedUrl != null ? unformattedUrl.getQuery() : null; + String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null; // Build the URI. Uri.Builder formattedUri = new Uri.Builder(); formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment); // Decode `formattedUri` as a `String` in `UTF-8`. - formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8"); + try { + formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8"); + } catch (UnsupportedEncodingException exception) { + // Load a blank string. + formattedUrlString = ""; + } } else if (unformattedUrlString.isEmpty()){ // Load a blank web site. // Load a blank string. formattedUrlString = ""; } else { // Search for the contents of the URL box. - // Sanitize the search input and convert it to a search. - final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8"); + // Create an encoded URL String. + String encodedUrlString; + + // Sanitize the search input. + try { + encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8"); + } catch (UnsupportedEncodingException exception) { + encodedUrlString = ""; + } // Add the base search URL. formattedUrlString = searchURL + encodedUrlString; @@ -3906,7 +4003,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false); hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false); translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true); - displayWebpageImagesBoolean = sharedPreferences.getBoolean("display_webpage_images", true); + downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false); // Apply the proxy through Orbot settings. applyProxyThroughOrbot(false); @@ -3982,7 +4079,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled. // The deprecated `.getDrawable()` must be used until the minimum API >= 21. @SuppressWarnings("deprecation") - private void applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) { + private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) { + // Get the current user agent. + String initialUserAgent = mainWebView.getSettings().getUserAgentString(); + + // Initialize a variable to track if the user agent changes. + boolean userAgentChanged = false; + // Parse the URL into a URI. Uri uri = Uri.parse(url); @@ -4073,13 +4176,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Store the general preference information. - String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); - String defaultUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser"); - defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value)); + String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value)); + defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)); boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); nightMode = sharedPreferences.getBoolean("night_mode", false); + boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true); - if (domainSettingsApplied) { // The url we are loading has custom domain settings. + if (domainSettingsApplied) { // The url has custom domain settings. // Get a cursor for the current host and move it to the first position. Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); currentHostDomainSettingsCursor.moveToFirst(); @@ -4102,7 +4206,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)); - displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); + int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); @@ -4218,25 +4322,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Set swipe to refresh. - switch (swipeToRefreshInt) { - case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT: - // Set swipe to refresh according to the default. - swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); - break; + // Store the applied user agent string, which is used in the View Source activity. + appliedUserAgentString = mainWebView.getSettings().getUserAgentString(); - case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED: - // Enable swipe to refresh. - swipeRefreshLayout.setEnabled(true); - break; + // Update the user agent change tracker. + userAgentChanged = !appliedUserAgentString.equals(initialUserAgent); + } - case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED: - // Disable swipe to refresh. - swipeRefreshLayout.setEnabled(false); - } + // Set swipe to refresh. + switch (swipeToRefreshInt) { + case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT: + // Set swipe to refresh according to the default. + swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); + break; - // Store the applied user agent string, which is used in the View Source activity. - appliedUserAgentString = mainWebView.getSettings().getUserAgentString(); + case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED: + // Enable swipe to refresh. + swipeRefreshLayout.setEnabled(true); + break; + + case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED: + // Disable swipe to refresh. + swipeRefreshLayout.setEnabled(false); + } + + // Set the loading of webpage images. + switch (displayWebpageImagesInt) { + case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT: + mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); + break; + + case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED: + mainWebView.getSettings().setLoadsImagesAutomatically(true); + break; + + case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED: + mainWebView.getSettings().setLoadsImagesAutomatically(false); + break; } // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. @@ -4247,15 +4369,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } else { // The new URL does not have custom domain settings. Load the defaults. // Store the values from `sharedPreferences` in variables. - javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false); - firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false); - thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false); - domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false); - saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); // Form data can be removed once the minimum API >= 26. + javaScriptEnabled = sharedPreferences.getBoolean("javascript", false); + firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false); + thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false); + domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false); + saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26. easyListEnabled = sharedPreferences.getBoolean("easylist", true); easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true); - fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboy_annoyance_list", true); - fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboy_social_blocking_list", true); + fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true); + fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true); ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true); blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false); @@ -4323,8 +4445,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store the applied user agent string, which is used in the View Source activity. appliedUserAgentString = mainWebView.getSettings().getUserAgentString(); + + // Update the user agent change tracker. + userAgentChanged = !appliedUserAgentString.equals(initialUserAgent); } + // Set the loading of webpage images. + mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); + // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent)); } @@ -4332,10 +4460,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Close the domains database helper. domainsDatabaseHelper.close(); - // Remove the `onTheFlyDisplayImagesSet` flag and set the display webpage images mode. `true` indicates that custom domain settings are applied. - onTheFlyDisplayImagesSet = false; - setDisplayWebpageImages(); - // Update the privacy icons, but only if `mainMenu` has already been populated. if (mainMenu != null) { updatePrivacyIcons(true); @@ -4346,6 +4470,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (reloadWebsite) { mainWebView.reload(); } + + // Return the user agent changed status. + return userAgentChanged; } private void applyProxyThroughOrbot(boolean reloadWebsite) { @@ -4353,12 +4480,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get the search preferences. - String homepageString = sharedPreferences.getString("homepage", "https://searx.me/"); - String torHomepageString = sharedPreferences.getString("tor_homepage", "http://ulrn6sryqaifefld.onion/"); - String torSearchString = sharedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q="); - String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", ""); - String searchString = sharedPreferences.getString("search", "https://searx.me/?q="); - String searchCustomUrlString = sharedPreferences.getString("search_custom_url", ""); + String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)); + String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)); + String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value)); + String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value)); + String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value)); + String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value)); // Set the homepage, search, and proxy options. if (proxyThroughOrbot) { // Set the Tor options. @@ -4437,28 +4564,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - private void setDisplayWebpageImages() { - if (!onTheFlyDisplayImagesSet) { - if (domainSettingsApplied) { // Custom domain settings are applied. - switch (displayWebpageImagesInt) { - case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT: - mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImagesBoolean); - break; - - case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED: - mainWebView.getSettings().setLoadsImagesAutomatically(true); - break; - - case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED: - mainWebView.getSettings().setLoadsImagesAutomatically(false); - break; - } - } else { // Default settings are applied. - mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImagesBoolean); - } - } - } - private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) { // Get handles for the menu items. MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript); @@ -4516,21 +4621,75 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + private void openUrlWithExternalApp(String url) { + // Create a download intent. Not specifying the action type will display the maximum number of options. + Intent downloadIntent = new Intent(); + + // Set the URI and the mime type. `"*/*"` will display the maximum number of options. + downloadIntent.setDataAndType(Uri.parse(url), "text/html"); + + // Flag the intent to open in a new task. + downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Show the chooser. + startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with))); + } + private void highlightUrlText() { + // Get the URL string. String urlString = urlTextBox.getText().toString(); - if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. - urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } + // Highlight the URL according to the protocol. + if (urlString.startsWith("file://")) { // This is a file URL. + // De-emphasize only the protocol. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else if (urlString.startsWith("content://")) { + // De-emphasize only the protocol. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // This is a web URL. + // Get the index of the `/` immediately after the domain name. + int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); + + // Create a base URL string. + String baseUrl; + + // Get the base URL. + if (endOfDomainName > 0) { // There is at least one character after the base URL. + // Get the base URL. + baseUrl = urlString.substring(0, endOfDomainName); + } else { // There are no characters after the base URL. + // Set the base URL to be the entire URL string. + baseUrl = urlString; + } + + // Get the index of the last `.` in the domain. + int lastDotIndex = baseUrl.lastIndexOf("."); + + // Get the index of the penultimate `.` in the domain. + int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1); + + // Markup the beginning of the URL. + if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. + urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - // Get the index of the `/` immediately after the domain name. - int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); + // De-emphasize subdomains. + if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. + urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted. + if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. + // De-emphasize the protocol and the additional subdomains. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // There is only one subdomain in the domain name. + // De-emphasize only the protocol. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + } - // De-emphasize the text after the domain name. - if (endOfDomainName > 0) { - urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + // De-emphasize the text after the domain name. + if (endOfDomainName > 0) { + urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } } }