import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipboardManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.net.http.SslCertificate;
import android.net.http.SslError;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
+import androidx.core.content.FileProvider;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.stoutner.privacybrowser.dialogs.OpenDialog;
import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
-import com.stoutner.privacybrowser.dialogs.SaveDialog;
+import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
- PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
+ PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
- // The executor service handles background tasks. It is accessed from `ViewSourceActivity`. TODO. Change the number of threads, or create a single thread executor.
+ // The executor service handles background tasks. It is accessed from `ViewSourceActivity`.
public static ExecutorService executorService = Executors.newFixedThreadPool(4);
// `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
// It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
public static String proxyMode = ProxyHelper.NONE;
-
- // The permission result request codes are used in `onCreateContextMenu()`, `onRequestPermissionResult()`, `onSaveWebpage()`, `onCloseStoragePermissionDialog()`, and `initializeWebView()`.
- private final int PERMISSION_OPEN_REQUEST_CODE = 0;
- private final int PERMISSION_SAVE_URL_REQUEST_CODE = 1;
- private final int PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE = 2;
- private final int PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE = 3;
-
// Define the saved instance state constants.
private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
private int savedTabPosition;
private String savedProxyMode;
- // Define the class views.
- private AppBarLayout appBarLayout;
- private TabLayout tabLayout;
- private ViewPager webViewPager;
-
// Define the class variables.
- private String newIntentUrl;
+ @SuppressWarnings("rawtypes")
+ AsyncTask populateBlocklists;
// The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
// `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
private String saveWebpageUrl;
private String saveWebpageFilePath;
+ // Declare the class views.
+ private DrawerLayout drawerLayout;
+ private AppBarLayout appBarLayout;
+ private TabLayout tabLayout;
+ private ViewPager webViewPager;
+
@Override
// Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
@SuppressLint("ClickableViewAccessibility")
// Get the screenshot preference.
String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
- boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
+ boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
// Get the theme entry values string array.
String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
setContentView(R.layout.main_framelayout);
// Get handles for the views.
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+ drawerLayout = findViewById(R.id.drawerlayout);
appBarLayout = findViewById(R.id.appbar_layout);
Toolbar toolbar = findViewById(R.id.toolbar);
tabLayout = findViewById(R.id.tablayout);
applyAppSettings();
// Populate the blocklists.
- new PopulateBlocklists(this, this).execute();
+ populateBlocklists = new PopulateBlocklists(this, this).execute();
}
@Override
// Replace the intent that started the app with this one.
setIntent(intent);
- // Process the intent here if Privacy Browser is fully initialized. If the process has been killed by the system while sitting in the background, this will be handled in `initializeWebView()`.
- if (ultraPrivacy != null) {
+ // Check to see if the app is being restarted.
+ if (savedStateArrayList == null || savedStateArrayList.size() == 0) { // The activity is running for the first time.
// Get the information from the intent.
String intentAction = intent.getAction();
Uri intentUriData = intent.getData();
String url;
// If the intent action is a web search, perform the search.
- if (isWebSearch) {
+ if (isWebSearch) { // The intent is a web search.
// Create an encoded URL string.
String encodedUrlString;
url = intentUriData.toString();
}
- // Check to see if the app is in the process of restarting
- if (savedStateArrayList == null) { // The app is not in the process of being restarted. Process the new intent.
- // Add a new tab if specified in the preferences.
- if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
- // Set the loading new intent flag.
- loadingNewIntent = true;
+ // Add a new tab if specified in the preferences.
+ if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
+ // Set the loading new intent flag.
+ loadingNewIntent = true;
- // Add a new tab.
- addNewTab(url, true);
- } else { // Load the URL in the current tab.
- // Make it so.
- loadUrl(currentWebView, url);
- }
- } else { // The app is being restarted. Store the URL, which will be processed in `finishedPopulatingBlocklists()`.
- newIntentUrl = url;
+ // Add a new tab.
+ addNewTab(url, true);
+ } else { // Load the URL in the current tab.
+ // Make it so.
+ loadUrl(currentWebView, url);
}
- // Get a handle for the drawer layout.
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-
// Close the navigation drawer if it is open.
if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
// Update the bookmarks drawer if returning from the Bookmarks activity.
if (restartFromBookmarksActivity) {
- // Get a handle for the drawer layout.
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-
// Close the bookmarks drawer.
drawerLayout.closeDrawer(GravityCompat.END);
bookmarksDatabaseHelper.close();
}
+ // Stop populating the blocklists if the AsyncTask is running in the background.
+ if (populateBlocklists != null) {
+ populateBlocklists.cancel(true);
+ }
+
// Run the default commands.
super.onDestroy();
}
}
@Override
- // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
+ // Remove Android Studio's warning about the dangers of enabling JavaScript. We know. Oh, how we know.
@SuppressLint("SetJavaScriptEnabled")
public boolean onOptionsItemSelected(MenuItem menuItem) {
// Get the selected menu item ID.
return true;
case R.id.bookmarks:
- // Get a handle for the drawer layout.
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-
// Open the bookmarks drawer.
drawerLayout.openDrawer(GravityCompat.END);
// Consume the event.
return true;
- case R.id.save_as_archive:
- // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getSettings().getUserAgentString(),
- currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
+ case R.id.save_archive:
+ // Instantiate the save dialog.
+ DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE, null, null, getString(R.string.webpage_mht), null,
+ false);
+
+ // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
+ saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
// Consume the event.
return true;
- case R.id.save_as_image:
- // Prepare the save dialog. The dialog will be displayed once the file size adn the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getSettings().getUserAgentString(),
- currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
+ case R.id.save_image:
+ // Instantiate the save dialog.
+ DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null,
+ false);
+
+ // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
+ saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
// Consume the event.
return true;
// Create the share intent.
Intent shareIntent = new Intent(Intent.ACTION_SEND);
+
+ // Add the share string to the intent.
shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
+
+ // Set the MIME type.
shareIntent.setType("text/plain");
+ // Set the intent to open in a new task.
+ shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
// Make it so.
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
break;
}
- // Get a handle for the drawer layout.
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-
// Close the navigation drawer.
drawerLayout.closeDrawer(GravityCompat.START);
return true;
// `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Make it so.
- startActivity(emailIntent);
+ try {
+ // Make it so.
+ startActivity(emailIntent);
+ } catch (ActivityNotFoundException exception) {
+ // Display a snackbar.
+ Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
+ }
// Consume the event.
return true;
bookmarksCursorAdapter.changeCursor(bookmarksCursor);
}
- // Override `onBackPressed` to handle the navigation drawer and and the WebViews.
+ // Override `onBackPressed()` to handle the navigation drawer and and the WebViews.
@Override
public void onBackPressed() {
- // Get a handle for the drawer layout.
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-
+ // Check the different options for processing `back`.
if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
// Close the navigation drawer.
drawerLayout.closeDrawer(GravityCompat.START);
} else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
- if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
- // close the bookmarks drawer.
- drawerLayout.closeDrawer(GravityCompat.END);
- } else { // A subfolder is displayed.
- // Place the former parent folder in `currentFolder`.
- currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
-
- // Load the new folder.
- loadBookmarksFolder();
- }
+ // close the bookmarks drawer.
+ drawerLayout.closeDrawer(GravityCompat.END);
} else if (displayingFullScreenVideo) { // A full screen video is shown.
// Get a handle for the layouts.
FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
} else { // Show the permission request directly.
// Request the write external storage permission. The file will be opened when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE);
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, StoragePermissionDialog.OPEN);
}
}
}
new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
break;
- case StoragePermissionDialog.SAVE_AS_ARCHIVE:
+ case StoragePermissionDialog.SAVE_ARCHIVE:
// Save the webpage archive.
- currentWebView.saveWebArchive(saveWebpageFilePath);
+ saveWebpageArchive();
break;
- case StoragePermissionDialog.SAVE_AS_IMAGE:
+ case StoragePermissionDialog.SAVE_IMAGE:
// Save the webpage image.
- new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
+ new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
break;
}
} else { // The storage permission has not been granted.
new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
break;
- case StoragePermissionDialog.SAVE_AS_ARCHIVE:
+ case StoragePermissionDialog.SAVE_ARCHIVE:
// Save the webpage archive.
- currentWebView.saveWebArchive(saveWebpageFilePath);
+ saveWebpageArchive();
break;
- case StoragePermissionDialog.SAVE_AS_IMAGE:
+ case StoragePermissionDialog.SAVE_IMAGE:
// Save the webpage image.
- new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
+ new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
break;
}
} else { // The file path is in a public directory.
// Show the storage permission alert dialog. The permission will be requested when the dialog is closed.
storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
} else { // Show the permission request directly.
- switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
- // Request the write external storage permission. The URL will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_URL_REQUEST_CODE);
-
- case StoragePermissionDialog.SAVE_AS_ARCHIVE:
- // Request the write external storage permission. The webpage archive will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE);
- break;
-
- case StoragePermissionDialog.SAVE_AS_IMAGE:
- // Request the write external storage permission. The webpage image will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE);
- break;
- }
+ // Request the write external storage permission according to the save type. The URL will be saved when it finishes.
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, saveType);
}
}
}
@Override
public void onCloseStoragePermissionDialog(int requestType) {
- switch (requestType) {
- case StoragePermissionDialog.OPEN:
- // Request the write external storage permission. The file will be opened when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE);
- break;
-
- case StoragePermissionDialog.SAVE_URL:
- // Request the write external storage permission. The URL will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_URL_REQUEST_CODE);
- break;
-
- case StoragePermissionDialog.SAVE_AS_ARCHIVE:
- // Request the write external storage permission. The webpage archive will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE);
- break;
+ // Request the write external storage permission according to the request type. The file will be opened when it finishes.
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestType);
- case StoragePermissionDialog.SAVE_AS_IMAGE:
- // Request the write external storage permission. The webpage image will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE);
- break;
- }
}
@Override
//Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included).
if (grantResults.length > 0) {
switch (requestCode) {
- case PERMISSION_OPEN_REQUEST_CODE:
+ case StoragePermissionDialog.OPEN:
// Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
// Load the file.
openFilePath = "";
break;
- case PERMISSION_SAVE_URL_REQUEST_CODE:
+ case StoragePermissionDialog.SAVE_URL:
// Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
// Save the raw URL.
saveWebpageFilePath = "";
break;
- case PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE:
+ case StoragePermissionDialog.SAVE_ARCHIVE:
// Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
// Save the webpage archive.
- currentWebView.saveWebArchive(saveWebpageFilePath);
+ saveWebpageArchive();
} else { // The storage permission was not granted.
// Display an error snackbar.
Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
saveWebpageFilePath = "";
break;
- case PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE:
+ case StoragePermissionDialog.SAVE_IMAGE:
// Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
// Save the webpage image.
- new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
+ new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
} else { // The storage permission was not granted.
// Display an error snackbar.
Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
// Get handles for views that need to be modified.
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
NavigationView navigationView = findViewById(R.id.navigationview);
SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
tab.select();
};
- // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated. TODO. Switch to a post command.
+ // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated. TODO.
selectTabHandler.postDelayed(selectTabRunnable, 150);
}
}
String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
+ String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
+ // Get the WebView theme entry values string array.
+ String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values);
+
// Get a handle for the cookie manager.
CookieManager cookieManager = CookieManager.getInstance();
// Set the WebView theme.
switch (webViewThemeInt) {
case DomainsDatabaseHelper.SYSTEM_DEFAULT:
- // // Ge the current system theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
- // Set the WebView theme according to the current system theme status.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode.
+ // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
+ if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected.
// Turn off the WebView dark mode.
WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
- } else { // The system is in night mode.
+ } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected.
// Turn on the WebView dark mode.
WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+ } else { // The system default theme is selected.
+ // Get the current system theme status.
+ int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+ // Set the WebView theme according to the current system theme status.
+ if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode.
+ // Turn off the WebView dark mode.
+ WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+ } else { // The system is in night mode.
+ // Turn on the WebView dark mode.
+ WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+ }
}
break;
nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true));
nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
- String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
// Apply the default first-party cookie setting.
cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
}
- // Get the WebView theme entry values string array.
- String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values);
-
// Apply the WebView theme if supported by the installed WebView.
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
// Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
webViewPager.setCurrentItem(savedTabPosition);
}
- // Process the new intent if it exists.
- if (newIntentUrl != null) {
+ // Get the intent that started the app.
+ Intent intent = getIntent();
+
+ // Reset the intent. This prevents a duplicate tab from being created on restart.
+ setIntent(new Intent());
+
+ // Get the information from the intent.
+ String intentAction = intent.getAction();
+ Uri intentUriData = intent.getData();
+
+ // Determine if this is a web search.
+ boolean isWebSearch = ((intentAction != null) && intentAction.equals(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 || isWebSearch) {
// Get the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ // Create a URL string.
+ String url;
+
+ // If the intent action is a web search, perform the search.
+ if (isWebSearch) { // The intent is a web search.
+ // Create an encoded URL string.
+ String encodedUrlString;
+
+ // Sanitize the search input and convert it to a search.
+ try {
+ encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
+ } catch (UnsupportedEncodingException exception) {
+ encodedUrlString = "";
+ }
+
+ // Add the base search URL.
+ url = searchURL + encodedUrlString;
+ } else { // The intent should contain a URL.
+ // Set the intent data as the url.
+ url = intentUriData.toString();
+ }
+
// Add a new tab if specified in the preferences.
if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
// Set the loading new intent flag.
loadingNewIntent = true;
// Add a new tab.
- addNewTab(newIntentUrl, true);
+ addNewTab(url, true);
} else { // Load the URL in the current tab.
// Make it so.
- loadUrl(currentWebView, newIntentUrl);
+ loadUrl(currentWebView, url);
}
-
- // Reset the new intent URL.
- newIntentUrl = null;
}
}
}
appBarLayout.setExpanded(true);
}
+ private void saveWebpageArchive() {
+ // Save the webpage archive.
+ currentWebView.saveWebArchive(saveWebpageFilePath);
+
+ // Display a snackbar.
+ Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + saveWebpageFilePath, Snackbar.LENGTH_SHORT);
+
+ // Add an open option to the snackbar.
+ saveWebpageArchiveSnackbar.setAction(R.string.open, (View view) -> {
+ // Get a file for the file name string.
+ File file = new File(saveWebpageFilePath);
+
+ // Declare a file URI variable.
+ Uri fileUri;
+
+ // Get the URI for the file according to the Android version.
+ if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
+ fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file);
+ } else { // Get the raw file path URI.
+ fileUri = Uri.fromFile(file);
+ }
+
+ // Get a handle for the content resolver.
+ ContentResolver contentResolver = getContentResolver();
+
+ // Create an open intent with `ACTION_VIEW`.
+ Intent openIntent = new Intent(Intent.ACTION_VIEW);
+
+ // Set the URI and the MIME type.
+ openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
+
+ // Allow the app to read the file URI.
+ openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // Show the chooser.
+ startActivity(Intent.createChooser(openIntent, getString(R.string.open)));
+ });
+
+ // Show the snackbar.
+ saveWebpageArchiveSnackbar.show();
+
+ // Reset the save Webpage file path.
+ saveWebpageFilePath = "";
+ }
+
private void clearAndExit() {
// Get a handle for the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
}
}
+ // Clear the logcat.
+ if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_logcat_key), true)) {
+ try {
+ // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system).
+ Process process = Runtime.getRuntime().exec("logcat -b all -c");
+
+ // Wait for the process to finish.
+ process.waitFor();
+ } catch (IOException|InterruptedException exception) {
+ // Do nothing.
+ }
+ }
+
// Clear the cache.
if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
// Clear the cache from each WebView.
// Clear the back/forward history for this WebView.
nestedScrollWebView.clearHistory();
- // Destroy the internal state of `mainWebView`.
+ // Destroy the internal state of the WebView.
nestedScrollWebView.destroy();
}
}
System.exit(0);
}
+ public void bookmarksBack(View view) {
+ if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
+ // close the bookmarks drawer.
+ drawerLayout.closeDrawer(GravityCompat.END);
+ } else { // A subfolder is displayed.
+ // Place the former parent folder in `currentFolder`.
+ currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
+
+ // Load the new folder.
+ loadBookmarksFolder();
+ }
+ }
+
private void setCurrentWebView(int pageNumber) {
// Get handles for the URL views.
RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
} else {
urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
}
- } else { // The fragment has not been populated. Try again in 100 milliseconds. //TODO try to replace this with a post command.
+ } else { // The fragment has not been populated. Try again in 100 milliseconds.
// Create a handler to set the current WebView.
Handler setCurrentWebViewHandler = new Handler();
// Get handles for the activity views.
FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
ActionBar actionBar = appCompatDelegate.getSupportActionBar();
LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
}
// Get the file name from the content disposition.
- String fileNameString = PrepareSaveDialog.getFileNameFromContentDisposition(this, contentDisposition, downloadUrl);
+ String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
// Instantiate the save dialog.
- DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+ DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
nestedScrollWebView.getAcceptFirstPartyCookies());
// Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
// Get the custom view from the tab.
View tabView = tab.getCustomView();
- // Remove the incorrect warning below that the current tab view might be null.
- assert tabView != null;
+ // Only populate the title text view if the tab view has been fully populated.
+ if (tabView != null) {
+ // Get the title text view from the tab.
+ TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
- // Get the title text view from the tab.
- TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
-
- // Set the title according to the URL.
- if (title.equals("about:blank")) {
- // Set the title to indicate a new tab.
- tabTitleTextView.setText(R.string.new_tab);
- } else {
- // Set the title as the tab text.
- tabTitleTextView.setText(title);
+ // Set the title according to the URL.
+ if (title.equals("about:blank")) {
+ // Set the title to indicate a new tab.
+ tabTitleTextView.setText(R.string.new_tab);
+ } else {
+ // Set the title as the tab text.
+ tabTitleTextView.setText(title);
+ }
}
}
}
// Open the email program in a new task instead of as part of Privacy Browser.
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Make it so.
- startActivity(emailIntent);
+ try {
+ // Make it so.
+ startActivity(emailIntent);
+ } catch (ActivityNotFoundException exception) {
+ // Display a snackbar.
+ Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
+ }
+
// Returning true indicates Privacy Browser is handling the URL by creating an intent.
return true;
// Open the dialer in a new task instead of as part of Privacy Browser.
dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Make it so.
- startActivity(dialIntent);
+ try {
+ // Make it so.
+ startActivity(dialIntent);
+ } catch (ActivityNotFoundException exception) {
+ // Display a snackbar.
+ Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
+ }
// Returning true indicates Privacy Browser is handling the URL by creating an intent.
return true;
}
}
- // Clear the cache and history if Incognito Mode is enabled.
+ // Clear the cache, history, and logcat if Incognito Mode is enabled.
if (incognitoModeEnabled) {
// Clear the cache. `true` includes disk files.
nestedScrollWebView.clearCache(true);
// Delete the secondary `Service Worker` cache directory.
// A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
- } catch (IOException e) {
+ } catch (IOException exception) {
// Do nothing if an error is thrown.
}
+
+ // Clear the logcat.
+ try {
+ // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system).
+ Runtime.getRuntime().exec("logcat -b all -c");
+ } catch (IOException exception) {
+ // Do nothing.
+ }
}
// Get the current page position.
// Get the intent that started the app.
Intent launchingIntent = getIntent();
+ // Reset the intent. This prevents a duplicate tab from being created on restart.
+ setIntent(new Intent());
+
// Get the information from the intent.
String launchingIntentAction = launchingIntent.getAction();
Uri launchingIntentUriData = launchingIntent.getData();