import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
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.SaveWebpageDialog;
// AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
- EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener,
- PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener, UrlHistoryDialog.NavigateHistoryListener,
- WebViewTabFragment.NewTabListener {
+ EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
+ PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
+ UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
// `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
public static String orbotStatus = "unknown";
public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
- // Start activity for result request codes.
- private final int FILE_UPLOAD_REQUEST_CODE = 0;
- public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1;
+ // Start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
+ public static final int BROWSE_OPEN_REQUEST_CODE = 0;
+ public static final int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1;
+ private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2;
+ // The permission result request codes are used in `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, `onSaveWebpage()`,
+ // `onCloseStoragePermissionDialog()`, and `initializeWebView()`.
+ private final int PERMISSION_DOWNLOAD_FILE_REQUEST_CODE = 0;
+ private final int PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE = 1;
+ private final int PERMISSION_OPEN_REQUEST_CODE = 2;
+ private final int PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE = 3;
+ private final int PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE = 4;
+
// The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
// `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
private NestedScrollWebView currentWebView;
// `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
private String downloadImageUrl;
- // The save webpage file path string is used in `onSaveWebpageImage()` and `onRequestPermissionResult()`
+ // The file path strings are used in `onSaveWebpageImage()` and `onRequestPermissionResult()`
+ private String openFilePath;
private String saveWebpageFilePath;
- // The permission result request codes are used in `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, `onSaveWebpageImage()`,
- // `onCloseStoragePermissionDialog()`, and `initializeWebView()`.
- private final int DOWNLOAD_FILE_REQUEST_CODE = 0;
- private final int DOWNLOAD_IMAGE_REQUEST_CODE = 1;
- private final int SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE = 2;
- private final int SAVE_WEBPAGE_IMAGE_REQUEST_CODE = 3;
-
@Override
// Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
@SuppressLint("ClickableViewAccessibility")
case R.id.save_as_archive:
// Instantiate the save webpage archive dialog.
- DialogFragment saveWebpageArchiveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.ARCHIVE);
+ DialogFragment saveWebpageArchiveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE);
// Show the save webpage archive dialog.
saveWebpageArchiveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_webpage));
case R.id.save_as_image:
// Instantiate the save webpage image dialog.
- DialogFragment saveWebpageImageDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.IMAGE);
+ DialogFragment saveWebpageImageDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE);
// Show the save webpage image dialog.
saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_webpage));
urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
break;
+ case R.id.open:
+ // Instantiate the open file dialog.
+ DialogFragment openDialogFragment = new OpenDialog();
+
+ // Show the open file dialog.
+ openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
+ break;
+
case R.id.requests:
// Populate the resource requests.
RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
downloadLocationPermissionDialogFragment.show(fragmentManager, 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);
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_FILE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
// Get a handle for the download file alert dialog.
downloadLocationPermissionDialogFragment.show(fragmentManager, 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);
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
// Get a handle for the download image alert dialog.
downloadLocationPermissionDialogFragment.show(fragmentManager, 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);
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
// Get a handle for the download image alert dialog.
switch (downloadType) {
case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
// Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_FILE_REQUEST_CODE);
break;
case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
// Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
- break;
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- // Get a handle for the fragment manager.
- FragmentManager fragmentManager = getSupportFragmentManager();
-
- switch (requestCode) {
- case DOWNLOAD_FILE_REQUEST_CODE:
- // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
- DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
-
- // On API 23, displaying the fragment must be delayed or the app will crash.
- if (Build.VERSION.SDK_INT == 23) {
- new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
- } else {
- downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
- }
-
- // Reset the download variables.
- downloadUrl = "";
- downloadContentDisposition = "";
- downloadContentLength = 0;
- break;
-
- case DOWNLOAD_IMAGE_REQUEST_CODE:
- // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
- DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
-
- // On API 23, displaying the fragment must be delayed or the app will crash.
- if (Build.VERSION.SDK_INT == 23) {
- new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
- } else {
- downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
- }
-
- // Reset the image URL variable.
- downloadImageUrl = "";
- break;
-
- case SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE:
- // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
- if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted.
- // Save the webpage archive.
- currentWebView.saveWebArchive(saveWebpageFilePath);
- } else {
- // 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 SAVE_WEBPAGE_IMAGE_REQUEST_CODE:
- // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
- if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted.
- // Save the webpage image.
- new SaveWebpageImage(this, currentWebView).execute(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 = "";
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE);
break;
}
}
// Process the results of a file browse.
@Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
// Run the default commands.
- super.onActivityResult(requestCode, resultCode, data);
+ super.onActivityResult(requestCode, resultCode, returnedIntent);
// Run the commands that correlate to the specified request code.
switch (requestCode) {
- case FILE_UPLOAD_REQUEST_CODE:
+ case BROWSE_FILE_UPLOAD_REQUEST_CODE:
// File uploads only work on API >= 21.
if (Build.VERSION.SDK_INT >= 21) {
// Pass the file to the WebView.
- fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
+ fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
}
break;
// Don't do anything if the user pressed back from the file picker.
if (resultCode == Activity.RESULT_OK) {
// Get a handle for the save dialog fragment.
- DialogFragment saveWebpageDialogFragment= (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_webpage));
+ DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_webpage));
// Only update the file name if the dialog still exists.
if (saveWebpageDialogFragment != null) {
FileNameHelper fileNameHelper = new FileNameHelper();
// Get the file path if it isn't null.
- if (data.getData() != null) {
+ if (returnedIntent.getData() != null) {
// Convert the file name URI to a file name path.
- String fileNamePath = fileNameHelper.convertUriToFileNamePath(data.getData());
+ String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
// Set the file name path as the text of the file name edit text.
fileNameEditText.setText(fileNamePath);
+
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNamePath.length());
+ }
+ }
+ }
+ break;
+
+ case BROWSE_OPEN_REQUEST_CODE:
+ // Don't do anything if the user pressed back from the file picker.
+ if (resultCode == Activity.RESULT_OK) {
+ // Get a handle for the open dialog fragment.
+ DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
+
+ // Only update the file name if the dialog still exists.
+ if (openDialogFragment != null) {
+ // Get a handle for the open dialog.
+ Dialog openDialog = openDialogFragment.getDialog();
+
+ // Remove the incorrect lint warning below tha tth edialog might be null.
+ assert openDialog != null;
+
+ // Get a handle for the file name edit text.
+ EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
+
+ // Instantiate the file name helper.
+ FileNameHelper fileNameHelper = new FileNameHelper();
+
+ // Get the file path if it isn't null.
+ if (returnedIntent.getData() != null) {
+ // Convert the file name URI to a file name path.
+ String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
+
+ // Set the file name path as the text of the file name edit text.
+ fileNameEditText.setText(fileNamePath);
+
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNamePath.length());
}
}
}
currentWebView.getSettings().setTextZoom(newFontSize);
}
+ @Override
+ public void onOpen(DialogFragment dialogFragment) {
+ // Get the dialog.
+ Dialog dialog = dialogFragment.getDialog();
+
+ // Remove the incorrect lint warning below that the dialog might be null.
+ assert dialog != null;
+
+ // Get a handle for the file name edit text.
+ EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
+
+ // Get the file path string.
+ openFilePath = fileNameEditText.getText().toString();
+
+ // 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.
+ currentWebView.loadUrl("file://" + openFilePath);
+ } else { // The storage permission has not been granted.
+ // Get the external private directory file.
+ File externalPrivateDirectoryFile = getExternalFilesDir(null);
+
+ // Remove the incorrect lint error below that the file might be null.
+ assert externalPrivateDirectoryFile != null;
+
+ // Get the external private directory string.
+ String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
+
+ // Check to see if the file path is in the external private directory.
+ if (openFilePath.startsWith(externalPrivateDirectory)) { // the file path is in the external private directory.
+ // Open the file.
+ currentWebView.loadUrl("file://" + openFilePath);
+ } 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.
+ // Instantiate the storage permission alert dialog.
+ DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.OPEN);
+
+ // Show the storage permission alert dialog. The permission will be requested the the dialog is closed.
+ 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);
+ }
+ }
+ }
+ }
+
@Override
public void onSaveWebpage(int saveType, DialogFragment dialogFragment) {
// Get the dialog.
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
//Save the webpage according to the save type.
switch (saveType) {
- case SaveWebpageDialog.ARCHIVE:
+ case StoragePermissionDialog.SAVE_ARCHIVE:
// Save the webpage archive.
currentWebView.saveWebArchive(saveWebpageFilePath);
break;
- case SaveWebpageDialog.IMAGE:
+ case StoragePermissionDialog.SAVE_IMAGE:
// Save the webpage image.
new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
break;
}
} else { // The storage permission has not been granted.
- // Get the external private directory `File`.
+ // Get the external private directory file.
File externalPrivateDirectoryFile = getExternalFilesDir(null);
// Remove the incorrect lint error below that the file might be null.
// Check to see if the file path is in the external private directory.
if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory.
- //Save the webpage according to the save type.
+ // Save the webpage according to the save type.
switch (saveType) {
- case SaveWebpageDialog.ARCHIVE:
+ case StoragePermissionDialog.SAVE_ARCHIVE:
// Save the webpage archive.
currentWebView.saveWebArchive(saveWebpageFilePath);
break;
- case SaveWebpageDialog.IMAGE:
+ case StoragePermissionDialog.SAVE_IMAGE:
// Save the webpage image.
new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
break;
storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
} else { // Show the permission request directly.
switch (saveType) {
- case SaveWebpageDialog.ARCHIVE:
+ case StoragePermissionDialog.SAVE_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}, SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE);
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE);
break;
- case SaveWebpageDialog.IMAGE:
+ case StoragePermissionDialog.SAVE_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}, SAVE_WEBPAGE_IMAGE_REQUEST_CODE);
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE);
break;
}
}
}
@Override
- public void onCloseStoragePermissionDialog(int saveType) {
- switch (saveType) {
- case SaveWebpageDialog.ARCHIVE:
+ 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_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}, SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE);
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE);
break;
- case SaveWebpageDialog.IMAGE:
+ case StoragePermissionDialog.SAVE_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}, SAVE_WEBPAGE_IMAGE_REQUEST_CODE);
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE);
+ break;
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ // Get a handle for the fragment manager.
+ FragmentManager fragmentManager = getSupportFragmentManager();
+
+ switch (requestCode) {
+ case PERMISSION_DOWNLOAD_FILE_REQUEST_CODE:
+ // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
+ DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
+
+ // On API 23, displaying the fragment must be delayed or the app will crash.
+ if (Build.VERSION.SDK_INT == 23) {
+ new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
+ } else {
+ downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
+ }
+
+ // Reset the download variables.
+ downloadUrl = "";
+ downloadContentDisposition = "";
+ downloadContentLength = 0;
+ break;
+
+ case PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE:
+ // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
+ DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
+
+ // On API 23, displaying the fragment must be delayed or the app will crash.
+ if (Build.VERSION.SDK_INT == 23) {
+ new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
+ } else {
+ downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+ }
+
+ // Reset the image URL variable.
+ downloadImageUrl = "";
+ break;
+
+ case PERMISSION_OPEN_REQUEST_CODE:
+ // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
+ if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted.
+ // Load the file.
+ currentWebView.loadUrl("file://" + openFilePath);
+ } 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 open file path.
+ openFilePath = "";
+ break;
+
+ case PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE:
+ // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
+ if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted.
+ // Save the webpage archive.
+ currentWebView.saveWebArchive(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_WEBPAGE_IMAGE_REQUEST_CODE:
+ // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
+ if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted.
+ // Save the webpage image.
+ new SaveWebpageImage(this, currentWebView).execute(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;
}
}
// Listen for touches on the navigation menu.
navigationView.setNavigationItemSelectedListener(this);
- // Get handles for the navigation menu and the back and forward menu items. The menu is zero-based.
+ // Get handles for the navigation menu and the back and forward menu items. The menu is 0 based.
Menu navigationMenu = navigationView.getMenu();
MenuItem navigationBackMenuItem = navigationMenu.getItem(2);
MenuItem navigationForwardMenuItem = navigationMenu.getItem(3);
MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4);
- MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5);
+ MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
// Update the web view pager every time a tab is modified.
webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
drawerHeaderPaddingBottom = (int) (8 * screenDensity);
- // The drawer listener is used to update the navigation menu.`
+ // The drawer listener is used to update the navigation menu.
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
String urlString = urlEditText.getText().toString();
// Highlight the URL according to the protocol.
- if (urlString.startsWith("file://")) { // This is a file URL.
- // De-emphasize only the protocol.
- urlEditText.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else if (urlString.startsWith("content://")) {
- // De-emphasize only the protocol.
- urlEditText.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ if (urlString.startsWith("file://") || urlString.startsWith("content://")) { // This is a file or content URL.
+ // De-emphasize everything before the file name.
+ urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,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));
downloadLocationPermissionDialogFragment.show(getSupportFragmentManager(), 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);
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_FILE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
// Get a handle for the download file alert dialog.
Intent fileChooserIntent = fileChooserParams.createIntent();
// Open the file chooser.
- startActivityForResult(fileChooserIntent, FILE_UPLOAD_REQUEST_CODE);
+ startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
}
return true;
}
Menu navigationMenu = navigationView.getMenu();
// Get a handle for the navigation requests menu item. The menu is 0 based.
- MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5);
+ MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
// Create an empty web resource response to be used if the resource request is blocked.
WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
--- /dev/null
+/*
+ * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.activities.MainWebViewActivity;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.PreferenceManager;
+
+public class OpenDialog extends DialogFragment {
+ // Define the open listener.
+ private OpenListener openListener;
+
+ // The public interface is used to send information back to the parent activity.
+ public interface OpenListener {
+ void onOpen(DialogFragment dialogFragment);
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ // Run the default commands.
+ super.onAttach(context);
+
+ // Get a handle for the open listener from the launching context.
+ openListener = (OpenListener) context;
+ }
+
+ // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
+ @SuppressLint("InflateParams")
+ @Override
+ @NonNull
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ // Get a handle for the activity and the context.
+ Activity activity = getActivity();
+ Context context = getContext();
+
+ // Remove the incorrect lint warnings below that the activity and the context might be null.
+ assert activity != null;
+ assert context != null;
+
+ // Get a handle for the shared preferences.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // Get the screenshot and theme preferences.
+ boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+ boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
+
+ // Use an alert dialog builder to create the alert dialog.
+ AlertDialog.Builder dialogBuilder;
+
+ // Set the style and icon according to the theme.
+ if (darkTheme) {
+ // Set the style.
+ dialogBuilder = new AlertDialog.Builder(activity, R.style.PrivacyBrowserAlertDialogDark);
+
+ // Set the icon.
+ dialogBuilder.setIcon(R.drawable.proxy_enabled_dark);
+ } else {
+ // Set the style.
+ dialogBuilder = new AlertDialog.Builder(activity, R.style.PrivacyBrowserAlertDialogLight);
+
+ // Set the icon.
+ dialogBuilder.setIcon(R.drawable.proxy_enabled_light);
+ }
+
+ // Set the title.
+ dialogBuilder.setTitle(R.string.open);
+
+ // Set the view. The parent view is null because it will be assigned by the alert dialog.
+ dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.open_dialog, null));
+
+ // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else.
+ dialogBuilder.setNegativeButton(R.string.cancel, null);
+
+ // Set the open button listener.
+ dialogBuilder.setPositiveButton(R.string.open, (DialogInterface dialog, int which) -> {
+ // Return the dialog fragment to the parent activity.
+ openListener.onOpen(this);
+ });
+
+ // Create an alert dialog from the builder.
+ AlertDialog alertDialog = dialogBuilder.create();
+
+ // Remove the incorrect lint warning below that the window might be null.
+ assert alertDialog.getWindow() != null;
+
+ // Disable screenshots if not allowed.
+ if (!allowScreenshots) {
+ alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+ }
+
+ // The alert dialog must be shown before items in the layout can be modified.
+ alertDialog.show();
+
+ // Get handles for the layout items.
+ EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext);
+ Button browseButton = alertDialog.findViewById(R.id.browse_button);
+ TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview);
+ Button openButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+ // Create a string for the default file path.
+ String defaultFilePath;
+
+ // Set the default file path according to the storage permission state.
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
+ // Set the default file path to use the external public directory.
+ defaultFilePath = Environment.getExternalStorageDirectory() + "/";
+ } else { // The storage permission has not been granted.
+ // Set the default file path to use the external private directory.
+ defaultFilePath = context.getExternalFilesDir(null) + "/";
+ }
+
+ // Display the default file path.
+ fileNameEditText.setText(defaultFilePath);
+
+ // Move the cursor to the end of the default file path.
+ fileNameEditText.setSelection(defaultFilePath.length());
+
+ // Update the status of the open button when the file name changes.
+ fileNameEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ // Do nothing.
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ // Enable the open button if a file name exists.
+ openButton.setEnabled(!fileNameEditText.getText().toString().isEmpty());
+ }
+ });
+
+ // Handle clicks on the browse button.
+ browseButton.setOnClickListener((View view) -> {
+ // Create the file picker intent.
+ Intent browseIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+ // Set the intent MIME type to include all files so that everything is visible.
+ browseIntent.setType("*/*");
+
+ // Set the initial directory if the minimum API >= 26.
+ if (Build.VERSION.SDK_INT >= 26) {
+ browseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
+ }
+
+ // Start the file picker. This must be started under `activity` to that the request code is returned correctly.
+ activity.startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_OPEN_REQUEST_CODE);
+ });
+
+ // Hide the storage permission text view on API < 23 as permissions on older devices are automatically granted.
+ if (Build.VERSION.SDK_INT < 23 || (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) {
+ storagePermissionTextView.setVisibility(View.GONE);
+ }
+
+ // Return the alert dialog.
+ return alertDialog;
+ }
+}