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=9107d5af73da0b4bbc3b01badde3ea66e963a0fb;hp=4280c56afc41168110921900cc448b750d211c26;hb=815a274380068235a5598f7495dd946605f48685;hpb=b82022327701273b1b56419e8d6042895c0bc7b9 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 4280c56a..9107d5af 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -31,6 +31,7 @@ import android.content.ActivityNotFoundException; 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; @@ -46,6 +47,7 @@ import android.graphics.drawable.Drawable; 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; @@ -102,6 +104,7 @@ import androidx.appcompat.widget.Toolbar; 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; @@ -135,7 +138,7 @@ import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; 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; @@ -174,10 +177,10 @@ import java.util.concurrent.Executors; 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()`. @@ -186,10 +189,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`. public static WebViewPagerAdapter webViewPagerAdapter; - // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`. They are used in `onRestart()`. - public static boolean loadUrlOnRestart; - public static String urlToLoadOnRestart; - // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`. public static boolean restartFromBookmarksActivity; @@ -215,13 +214,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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"; @@ -234,13 +226,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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()`. @@ -336,6 +324,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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") @@ -360,7 +354,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -399,7 +393,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -443,7 +437,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook applyAppSettings(); // Populate the blocklists. - new PopulateBlocklists(this, this).execute(); + populateBlocklists = new PopulateBlocklists(this, this).execute(); } @Override @@ -454,8 +448,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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(); @@ -472,7 +466,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; @@ -490,26 +484,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -566,20 +552,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Load the URL on restart (used when loading a bookmark). - if (loadUrlOnRestart) { - // Load the specified URL. - loadUrl(currentWebView, urlToLoadOnRestart); - - // Reset the load on restart tracker. - loadUrlOnRestart = false; - } - // 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); @@ -738,6 +712,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(); } @@ -1081,7 +1060,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @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. @@ -1130,9 +1109,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -1729,18 +1705,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -1776,9 +1758,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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))); @@ -2109,9 +2098,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; } - // Get a handle for the drawer layout. - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); - // Close the navigation drawer. drawerLayout.closeDrawer(GravityCompat.START); return true; @@ -2144,7 +2130,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { - // Store the hit test result. + // Get the hit test result. final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult(); // Define the URL strings. @@ -2232,8 +2218,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the image URL. imageUrl = hitTestResult.getExtra(); - // Set the image URL as the title of the context menu. - menu.setHeaderTitle(imageUrl); + // Remove the incorrect lint warning below that the image URL might be null. + assert imageUrl != null; + + // Set the context menu title. + if (imageUrl.startsWith("data:")) { // The image data is contained in within the URL, making it exceedingly long. + // Truncate the image URL before making it the title. + menu.setHeaderTitle(imageUrl.substring(0, 100)); + } else { // The image URL does not contain the full image data. + // Set the image URL as the title of the context menu. + menu.setHeaderTitle(imageUrl); + } // Add an Open in New Tab entry. menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { @@ -2425,8 +2420,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `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; @@ -2647,26 +2647,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -3002,6 +2992,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the file path string. openFilePath = fileNameEditText.getText().toString(); + // Apply the domain settings. This resets the favorite icon and removes any domain settings. + applyDomainSettings(currentWebView, "file://" + openFilePath, true, false); + // Check to see if the storage permission is needed. if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. // Open the file. @@ -3030,14 +3023,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); } } } } @Override - public void onSaveWebpage(int saveType, DialogFragment dialogFragment) { + public void onSaveWebpage(int saveType, String originalUrlString, DialogFragment dialogFragment) { // Get the dialog. Dialog dialog = dialogFragment.getDialog(); @@ -3048,8 +3041,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook EditText urlEditText = dialog.findViewById(R.id.url_edittext); EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); - // Get the strings from the edit texts. - saveWebpageUrl = urlEditText.getText().toString(); + // Store the URL. + if ((originalUrlString != null) && originalUrlString.startsWith("data:")) { + // Save the original URL. + saveWebpageUrl = originalUrlString; + } else { + // Get the URL from the edit text, which may have been modified. + saveWebpageUrl = urlEditText.getText().toString(); + } + + // Get the file path from the edit text. saveWebpageFilePath = fileNameEditText.getText().toString(); // Check to see if the storage permission is needed. @@ -3061,16 +3062,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(saveWebpageFilePath); 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; } + + // Reset the strings. + saveWebpageUrl = ""; + saveWebpageFilePath = ""; } else { // The storage permission has not been granted. // Get the external private directory file. File externalPrivateDirectoryFile = getExternalFilesDir(null); @@ -3090,16 +3095,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(saveWebpageFilePath); 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; } + + // Reset the strings. + saveWebpageUrl = ""; + saveWebpageFilePath = ""; } else { // The file path is in a public directory. // Check if the user has previously denied the storage permission. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. @@ -3109,21 +3118,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); } } } @@ -3131,27 +3127,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @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; + // 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_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; - - 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 @@ -3159,7 +3137,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook //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. @@ -3168,12 +3146,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display an error snackbar. Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); } - - // Reset the open file path. - 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. @@ -3182,40 +3157,35 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display an error snackbar. Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); } - - // Reset the save strings. - saveWebpageUrl = ""; - 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(saveWebpageFilePath); } else { // The storage permission was not granted. // Display an error snackbar. Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); } - - // Reset the save webpage file path. - 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(); } - - // Reset the save webpage file path. - saveWebpageFilePath = ""; break; } + + // Reset the strings. + openFilePath = ""; + saveWebpageUrl = ""; + saveWebpageFilePath = ""; } } @@ -3331,7 +3301,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -3367,11 +3336,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled by creating a new tab. if (tabLayout.getSelectedTabPosition() != position) { - // Create a handler to select the tab. - Handler selectTabHandler = new Handler(); - - // Create a runnable to select the tab. - Runnable selectTabRunnable = () -> { + // Wait until the new tab has been created. + tabLayout.post(() -> { // Get a handle for the tab. TabLayout.Tab tab = tabLayout.getTabAt(position); @@ -3380,10 +3346,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Select the tab. 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. - selectTabHandler.postDelayed(selectTabRunnable, 150); + }); } } @@ -3948,9 +3911,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(); @@ -4149,16 +4116,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -4227,7 +4203,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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()); @@ -4291,9 +4266,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -4783,25 +4755,57 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; } } } @@ -4842,6 +4846,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void closeCurrentTab() { + // Pause the current WebView. + currentWebView.onPause(); + + // Pause the current WebView JavaScript timers. + currentWebView.pauseTimers(); + // Get the current tab number. int currentTabNumber = tabLayout.getSelectedTabPosition(); @@ -4857,6 +4867,48 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook appBarLayout.setExpanded(true); } + private void saveWebpageArchive(String filePath) { + // Save the webpage archive. + currentWebView.saveWebArchive(filePath); + + // Display a snackbar. + Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + filePath, 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(filePath); + + // 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(); + } + private void clearAndExit() { // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -4945,6 +4997,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + // 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. @@ -5001,7 +5066,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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(); } } @@ -5034,6 +5099,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -5124,7 +5202,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } 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(); @@ -5187,7 +5265,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -5341,10 +5418,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // 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. @@ -5494,19 +5571,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; - - // Get the title text view from the tab. - TextView tabTitleTextView = tabView.findViewById(R.id.title_textview); + // 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); - // 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); + } } } } @@ -5679,8 +5756,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -5694,8 +5777,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -6207,7 +6295,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // 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); @@ -6227,9 +6315,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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. @@ -6371,6 +6467,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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();