From: Soren Stoutner Date: Tue, 31 Dec 2019 21:14:49 +0000 (-0700) Subject: Implement opening of local files. https://redmine.stoutner.com/issues/513 X-Git-Tag: v3.3~1 X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff_plain;h=b235ba52731f112015d9058ade4c5a66abf7ed5c;hp=5d3cafb4a4fbb2bf851d36f973cc1e8b23ecebab Implement opening of local files. https://redmine.stoutner.com/issues/513 --- 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 efa66ac9..83bd942d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -131,6 +131,7 @@ import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; 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; @@ -170,9 +171,9 @@ import java.util.Set; // 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"; @@ -199,11 +200,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; @@ -312,16 +322,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `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") @@ -1702,7 +1706,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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)); @@ -1712,7 +1716,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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)); @@ -1863,6 +1867,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(); @@ -2119,7 +2131,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -2225,7 +2237,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -2350,7 +2362,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -2644,80 +2656,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; } } @@ -2966,17 +2910,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -2984,7 +2928,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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) { @@ -3001,12 +2945,50 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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()); } } } @@ -3160,6 +3142,54 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -3178,18 +3208,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -3200,14 +3230,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -3222,14 +3252,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; } } @@ -3238,16 +3268,103 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @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; } } @@ -3529,12 +3646,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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() { @@ -3796,7 +3913,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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) { @@ -4587,12 +4704,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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)); @@ -5330,7 +5444,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -5627,7 +5741,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Intent fileChooserIntent = fileChooserParams.createIntent(); // Open the file chooser. - startActivityForResult(fileChooserIntent, FILE_UPLOAD_REQUEST_CODE); + startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); } return true; } @@ -5743,7 +5857,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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())); diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java index 4da0f14e..f89649bc 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java @@ -176,40 +176,42 @@ public class CreateBookmarkDialog extends DialogFragment { // Set the current `WebView` title as the text for `create_bookmark_name_edittext`. createBookmarkNameEditText.setText(title); - // Allow the `enter` key on the keyboard to create the bookmark from `create_bookmark_name_edittext`. - createBookmarkNameEditText.setOnKeyListener((View view, int keyCode, KeyEvent event) -> { - // If the event is a key-down on the `enter` key, select the `PositiveButton` `Create`. - if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { - // Trigger `createBookmarkListener` and return the `DialogFragment` to the parent activity. + // Allow the `enter` key on the keyboard to create the bookmark from the create bookmark name edittext`. + createBookmarkNameEditText.setOnKeyListener((View view, int keyCode, KeyEvent keyEvent) -> { + // If the event is a key-down on the `enter` key, select the create button. + if ((keyCode == KeyEvent.KEYCODE_ENTER) && (keyEvent.getAction() == KeyEvent.ACTION_DOWN)) { + // Trigger the create bookmark listener and return the dialog fragment and the favorite icon bitmap to the parent activity. createBookmarkListener.onCreateBookmark(this, favoriteIconBitmap); - // Manually dismiss the `AlertDialog`. + // Manually dismiss the alert dialog. alertDialog.dismiss(); // Consume the event. return true; - } else { // If any other key was pressed, do not consume the event. + } else { // Some other key was pressed. + // Do not consume the event. return false; } }); - // Set the formattedUrlString as the initial text of `create_bookmark_url_edittext`. + // Set the formatted URL string as the initial text of the create bookmark URL edit text. EditText createBookmarkUrlEditText = alertDialog.findViewById(R.id.create_bookmark_url_edittext); createBookmarkUrlEditText.setText(url); - // Allow the `enter` key on the keyboard to create the bookmark from `create_bookmark_url_edittext`. - createBookmarkUrlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> { - // If the event is a key-down on the "enter" key, select the PositiveButton "Create". - if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { - // Trigger `createBookmarkListener` and return the DialogFragment to the parent activity. + // Allow the enter key on the keyboard to create the bookmark from create bookmark URL edit text. + createBookmarkUrlEditText.setOnKeyListener((View v, int keyCode, KeyEvent keyEvent) -> { + // If the event is a key-down on the `enter` key, select the create button. + if ((keyCode == KeyEvent.KEYCODE_ENTER) && (keyEvent.getAction() == KeyEvent.ACTION_DOWN)) { + // Trigger the create bookmark listener and return the dialog fragment and the favorite icon bitmap to the parent activity. createBookmarkListener.onCreateBookmark(this, favoriteIconBitmap); - // Manually dismiss the `AlertDialog`. + // Manually dismiss the alert dialog. alertDialog.dismiss(); // Consume the event. return true; - } else { // If any other key was pressed, do not consume the event. + } else { // Some other key was pressed. + // Do not consume the event. return false; } }); diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.java new file mode 100644 index 00000000..b478d726 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.java @@ -0,0 +1,205 @@ +/* + * Copyright © 2019 Soren Stoutner . + * + * This file is part of 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 . + */ + +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; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java index 1c24e0be..72d0f20a 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java @@ -50,10 +50,6 @@ import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.activities.MainWebViewActivity; public class SaveWebpageDialog extends DialogFragment { - // Define the save type constants. - public static final int ARCHIVE = 0; - public static final int IMAGE = 1; - // Define the save webpage listener. private SaveWebpageListener saveWebpageListener; @@ -88,7 +84,7 @@ public class SaveWebpageDialog extends DialogFragment { return saveWebpageDialog; } - // `@SuppressLing("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog. + // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog. @SuppressLint("InflateParams") @Override @NonNull @@ -127,11 +123,11 @@ public class SaveWebpageDialog extends DialogFragment { // Set the icon according to the save type. switch (saveType) { - case ARCHIVE: + case StoragePermissionDialog.SAVE_ARCHIVE: dialogBuilder.setIcon(R.drawable.dom_storage_cleared_dark); break; - case IMAGE: + case StoragePermissionDialog.SAVE_IMAGE: dialogBuilder.setIcon(R.drawable.images_enabled_dark); break; } @@ -141,11 +137,11 @@ public class SaveWebpageDialog extends DialogFragment { // Set the icon according to the save type. switch (saveType) { - case ARCHIVE: + case StoragePermissionDialog.SAVE_ARCHIVE: dialogBuilder.setIcon(R.drawable.dom_storage_cleared_light); break; - case IMAGE: + case StoragePermissionDialog.SAVE_IMAGE: dialogBuilder.setIcon(R.drawable.images_enabled_light); break; } @@ -153,11 +149,11 @@ public class SaveWebpageDialog extends DialogFragment { // Set the title according to the type. switch (saveType) { - case ARCHIVE: + case StoragePermissionDialog.SAVE_ARCHIVE: dialogBuilder.setTitle(R.string.save_archive); break; - case IMAGE: + case StoragePermissionDialog.SAVE_IMAGE: dialogBuilder.setTitle(R.string.save_image); break; } @@ -177,7 +173,7 @@ public class SaveWebpageDialog extends DialogFragment { // Create an alert dialog from the builder. AlertDialog alertDialog = dialogBuilder.create(); - // Remove the incorrect lint warning below that `getWindow()` might be null. + // Remove the incorrect lint warning below that the window might be null. assert alertDialog.getWindow() != null; // Disable screenshots if not allowed. @@ -199,11 +195,11 @@ public class SaveWebpageDialog extends DialogFragment { // Set the default file name according to the type. switch (saveType) { - case ARCHIVE: + case StoragePermissionDialog.SAVE_ARCHIVE: defaultFileName = getString(R.string.webpage_mht); break; - case IMAGE: + case StoragePermissionDialog.SAVE_IMAGE: defaultFileName = getString(R.string.webpage_png); break; } @@ -223,6 +219,9 @@ public class SaveWebpageDialog extends DialogFragment { // 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 save button when the file name changes. fileNameEditText.addTextChangedListener(new TextWatcher() { @Override @@ -237,7 +236,7 @@ public class SaveWebpageDialog extends DialogFragment { @Override public void afterTextChanged(Editable s) { - // // Enable the save button if a file name exists. + // Enable the save button if a file name exists. saveButton.setEnabled(!fileNameEditText.getText().toString().isEmpty()); } }); @@ -252,11 +251,11 @@ public class SaveWebpageDialog extends DialogFragment { // Set the initial file name according to the type. switch (saveType) { - case ARCHIVE: + case StoragePermissionDialog.SAVE_ARCHIVE: browseIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.webpage_mht)); break; - case IMAGE: + case StoragePermissionDialog.OPEN: browseIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.webpage_png)); break; } @@ -274,7 +273,7 @@ public class SaveWebpageDialog extends DialogFragment { }); // Hide the storage permission text view on API < 23 as permissions on older devices are automatically granted. - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 23 || (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) { storagePermissionTextView.setVisibility(View.GONE); } diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java index 1ee03b8f..010dbcf6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java @@ -34,12 +34,17 @@ import androidx.fragment.app.DialogFragment; import com.stoutner.privacybrowser.R; public class StoragePermissionDialog extends DialogFragment { + // Define the save type constants. + public static final int OPEN = 0; + public static final int SAVE_ARCHIVE = 1; + public static final int SAVE_IMAGE = 2; + // The listener is used in `onAttach()` and `onCreateDialog()`. private StoragePermissionDialogListener storagePermissionDialogListener; // The public interface is used to send information back to the parent activity. public interface StoragePermissionDialogListener { - void onCloseStoragePermissionDialog(int saveType); + void onCloseStoragePermissionDialog(int requestType); } @Override @@ -51,12 +56,12 @@ public class StoragePermissionDialog extends DialogFragment { storagePermissionDialogListener = (StoragePermissionDialogListener) context; } - public static StoragePermissionDialog displayDialog(int saveType) { + public static StoragePermissionDialog displayDialog(int requestType) { // Create an arguments bundle. Bundle argumentsBundle = new Bundle(); // Store the save type in the bundle. - argumentsBundle.putInt("save_type", saveType); + argumentsBundle.putInt("request_type", requestType); // Create a new instance of the storage permission dialog. StoragePermissionDialog storagePermissionDialog = new StoragePermissionDialog(); @@ -78,7 +83,7 @@ public class StoragePermissionDialog extends DialogFragment { assert arguments != null; // Get the save type. - int saveType = arguments.getInt("save_type"); + int requestType = arguments.getInt("request_type"); // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); @@ -108,7 +113,7 @@ public class StoragePermissionDialog extends DialogFragment { // Set an listener on the OK button. dialogBuilder.setNegativeButton(R.string.ok, (DialogInterface dialog, int which) -> { // Inform the parent activity that the dialog was closed. - storagePermissionDialogListener.onCloseStoragePermissionDialog(saveType); + storagePermissionDialogListener.onCloseStoragePermissionDialog(requestType); }); // Create an alert dialog from the builder. diff --git a/app/src/main/res/layout/open_dialog.xml b/app/src/main/res/layout/open_dialog.xml new file mode 100644 index 00000000..dd3059e3 --- /dev/null +++ b/app/src/main/res/layout/open_dialog.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + +