Replace Android's download manager. https://redmine.stoutner.com/issues/528
authorSoren Stoutner <soren@stoutner.com>
Fri, 13 Mar 2020 07:15:16 +0000 (00:15 -0700)
committerSoren Stoutner <soren@stoutner.com>
Fri, 13 Mar 2020 07:15:16 +0000 (00:15 -0700)
54 files changed:
.idea/codeStyles/Project.xml
.idea/gradle.xml
.idea/modules.xml
app/build.gradle
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFileDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImageDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadLocationPermissionDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java
app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java
app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java
app/src/main/res/drawable/about_dark.xml
app/src/main/res/drawable/about_light.xml
app/src/main/res/drawable/add_dark.xml
app/src/main/res/drawable/add_light.xml
app/src/main/res/drawable/clear_and_exit.xml [new file with mode: 0644]
app/src/main/res/drawable/downloads.xml [new file with mode: 0644]
app/src/main/res/drawable/downloads_dark.xml [deleted file]
app/src/main/res/drawable/downloads_light.xml [deleted file]
app/src/main/res/drawable/home_enabled_dark.xml
app/src/main/res/drawable/home_enabled_light.xml
app/src/main/res/drawable/home_ghosted_dark.xml [deleted file]
app/src/main/res/drawable/home_ghosted_light.xml [deleted file]
app/src/main/res/drawable/open_with_external_app_disabled_dark.xml [deleted file]
app/src/main/res/drawable/open_with_external_app_disabled_light.xml [deleted file]
app/src/main/res/drawable/open_with_external_app_enabled_dark.xml [deleted file]
app/src/main/res/drawable/open_with_external_app_enabled_light.xml [deleted file]
app/src/main/res/drawable/search_enabled_dark.xml
app/src/main/res/drawable/search_enabled_light.xml
app/src/main/res/drawable/search_ghosted_dark.xml [deleted file]
app/src/main/res/drawable/search_ghosted_light.xml [deleted file]
app/src/main/res/layout/download_file_dialog.xml [deleted file]
app/src/main/res/layout/download_image_dialog.xml [deleted file]
app/src/main/res/layout/save_dialog.xml [new file with mode: 0644]
app/src/main/res/layout/save_webpage_dialog.xml [deleted file]
app/src/main/res/menu/logcat_options_menu.xml
app/src/main/res/menu/webview_navigation_menu.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-fr/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values-tr/strings.xml
app/src/main/res/values-w820dp/dimens.xml [deleted file]
app/src/main/res/values/dimens.xml [deleted file]
app/src/main/res/values/strings.xml
app/src/main/res/values/styles.xml
app/src/main/res/xml/preferences.xml
build.gradle
gradle/wrapper/gradle-wrapper.properties

index add4e241c97841feb5dd41001ed961d2e7569b32..d7666805610b03c9e373db3a2920c57c835ba179 100644 (file)
@@ -1,9 +1,6 @@
 <component name="ProjectCodeStyleConfiguration">
   <code_scheme name="Project" version="173">
     <option name="RIGHT_MARGIN" value="210" />
-    <AndroidXmlCodeStyleSettings>
-      <option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
-    </AndroidXmlCodeStyleSettings>
     <JavaCodeStyleSettings>
       <option name="IMPORT_LAYOUT_TABLE">
         <value>
index 0d3cc348040e7e3839ce47d0a0506154c3405be4..74e816d0662b39a08e7d86d3cbff35ad186f877e 100644 (file)
@@ -1,8 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
+        <option name="delegatedBuild" value="false" />
+        <option name="testRunner" value="PLATFORM" />
         <option name="distributionType" value="LOCAL" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.14.1" />
@@ -13,7 +16,6 @@
           </set>
         </option>
         <option name="resolveModulePerSourceSet" value="false" />
-        <option name="testRunner" value="PLATFORM" />
       </GradleProjectSettings>
     </option>
   </component>
index 46c3407bf282a6f440c09df2b8d2ec58b22ac7a5..93f58a3b3f6e36769f3aa9ca0f4245eb20d8e8d6 100644 (file)
@@ -2,8 +2,8 @@
 <project version="4">
   <component name="ProjectModuleManager">
     <modules>
-      <module fileurl="file://$PROJECT_DIR$/PrivacyBrowser.iml" filepath="$PROJECT_DIR$/PrivacyBrowser.iml" />
-      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
+      <module fileurl="file://$PROJECT_DIR$/PrivacyBrowser.iml" filepath="$PROJECT_DIR$/PrivacyBrowser.iml" group="PrivacyBrowser" />
+      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" group="PrivacyBrowser/app" />
     </modules>
   </component>
 </project>
\ No newline at end of file
index 5ca89f82384ef4b2bb014b3d7b83a28eda8c4d88..5c9bb272e8fe8c3ffbbfb5ec5fa05c21424c10a5 100644 (file)
@@ -18,8 +18,8 @@
  */
 
 apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android-extensions'
 apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
 
 android {
     compileSdkVersion 29
@@ -30,6 +30,9 @@ android {
         targetSdkVersion 29
         versionCode 47
         versionName "3.3"
+
+        // The `multiDexEnabled` entry could possibly be removed once the `minSdkVersion` is >= 21.
+        multiDexEnabled true
     }
 
     buildTypes {
@@ -79,19 +82,19 @@ dependencies {
     implementation 'androidx.appcompat:appcompat:1.1.0'
     implementation 'androidx.cardview:cardview:1.0.0'
     implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
-    implementation "androidx.core:core-ktx:1.1.0"
+    implementation "androidx.core:core-ktx:1.2.0"
     implementation 'androidx.drawerlayout:drawerlayout:1.0.0'
     implementation 'androidx.preference:preference:1.1.0'
     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
     implementation 'androidx.viewpager:viewpager:1.0.0'
-    implementation 'androidx.webkit:webkit:1.1.0'
+    implementation 'androidx.webkit:webkit:1.2.0'
 
     // Include the Kotlin standard libraries
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61"
 
     // Include the Google material library.
-    implementation 'com.google.android.material:material:1.0.0'
+    implementation 'com.google.android.material:material:1.1.0'
 
     // Only compile Firebase ads for the free flavor.
-    freeImplementation 'com.google.firebase:firebase-ads:18.3.0'
+    freeImplementation 'com.google.firebase:firebase-ads:19.0.0'
 }
\ No newline at end of file
index 5e4d3eeb6a5e1360978659fd027e716985c8edf8..c8f8789a69105e2590d5465816142ef96d89a88c 100644 (file)
@@ -48,7 +48,6 @@ import android.net.http.SslCertificate;
 import android.net.http.SslError;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.preference.PreferenceManager;
@@ -104,7 +103,6 @@ import androidx.core.content.ContextCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentManager;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import androidx.viewpager.widget.ViewPager;
 
@@ -125,9 +123,6 @@ import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
-import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
-import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
-import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
@@ -135,7 +130,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.SaveWebpageDialog;
+import com.stoutner.privacybrowser.dialogs.SaveDialog;
 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
 import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
@@ -169,12 +164,10 @@ import java.util.Map;
 import java.util.Objects;
 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, OpenDialog.OpenListener,
-        PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
-        UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
+        EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener,
+        OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.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";
@@ -212,14 +205,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     public static String proxyMode = ProxyHelper.NONE;
 
 
-    // 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;
-    private final int PERMISSION_SAVE_WEBPAGE_RAW_REQUEST_CODE = 5;
+    // 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;
 
     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
@@ -317,15 +307,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private boolean sanitizeFacebookClickIds;
     private boolean sanitizeTwitterAmpRedirects;
 
-    // The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
-    private String downloadUrl;
-    private String downloadContentDisposition;
-    private long downloadContentLength;
-
-    // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
-    private String downloadImageUrl;
-
-    // The file path strings are used in `onSaveWebpageImage()` and `onRequestPermissionResult()`
+    // The file path strings are used in `onSaveWebpage()` and `onRequestPermissionResult()`
     private String openFilePath;
     private String saveWebpageUrl;
     private String saveWebpageFilePath;
@@ -1710,7 +1692,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             case R.id.save_url:
                 // Instantiate the save dialog.
-                DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, currentWebView.getCurrentUrl());
+                DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptFirstPartyCookies());
 
                 // Show the save dialog.  It must be named `save_dialog` so that the file picked can update the file name.
                 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
@@ -1720,7 +1703,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             case R.id.save_as_archive:
                 // Instantiate the save webpage archive dialog.
-                DialogFragment saveWebpageArchiveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getCurrentUrl());
+                DialogFragment saveWebpageArchiveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptFirstPartyCookies());
 
                 // Show the save webpage archive dialog.  It must be named `save_dialog` so that the file picked can update the file name.
                 saveWebpageArchiveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
@@ -1730,7 +1714,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             case R.id.save_as_image:
                 // Instantiate the save webpage image dialog.  It must be named `save_webpage` so that the file picked can update the file name.
-                DialogFragment saveWebpageImageDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getCurrentUrl());
+                DialogFragment saveWebpageImageDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptFirstPartyCookies());
 
                 // Show the save webpage image dialog.  It must be named `save_dialog` so that the file picked can update the file name.
                 saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
@@ -2124,9 +2109,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Add a Save URL entry.
                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
                     // Instantiate the save dialog.
-                    DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, linkUrl);
+                    DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, linkUrl, currentWebView.getSettings().getUserAgentString(),
+                            currentWebView.getAcceptFirstPartyCookies());
 
-                    // Show the save dialog.  It must be named `save_dialog` so that the file picked can update the file name.
+                    // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
                     saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
 
                     // Consume the event.
@@ -2207,7 +2193,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Add a Save Image entry.
                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
                    // Instantiate the save dialog.
-                   DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, imageUrl);
+                   DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, imageUrl, currentWebView.getSettings().getUserAgentString(),
+                           currentWebView.getAcceptFirstPartyCookies());
 
                    // Show the save dialog.  It must be named `save_dialog` so that the file picked can update the file name.
                     saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
@@ -2320,7 +2307,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
                     // Instantiate the save  dialog.
-                    DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, imageUrl);
+                    DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, imageUrl, currentWebView.getSettings().getUserAgentString(),
+                            currentWebView.getAcceptFirstPartyCookies());
 
                     // Show the save raw dialog.  It must be named `save_dialog` so that the file picked can update the file name.
                     saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
@@ -2331,7 +2319,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
                     // Instantiate the save dialog.
-                    DialogFragment saveDialogFragment = SaveWebpageDialog.saveUrl(StoragePermissionDialog.SAVE, linkUrl);
+                    DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, linkUrl, currentWebView.getSettings().getUserAgentString(),
+                            currentWebView.getAcceptFirstPartyCookies());
 
                     // Show the save raw dialog.  It must be named `save_dialog` so that the file picked can update the file name.
                     saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
@@ -2602,143 +2591,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
     }
 
-    @Override
-    public void onCloseDownloadLocationPermissionDialog(int downloadType) {
-        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}, 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}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE);
-                break;
-        }
-    }
-
-    @Override
-    public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
-        // Download the image if it has an HTTP or HTTPS URI.
-        if (imageUrl.startsWith("http")) {
-            // Get a handle for the system `DOWNLOAD_SERVICE`.
-            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
-
-            // Parse `imageUrl`.
-            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
-
-            // Get a handle for the cookie manager.
-            CookieManager cookieManager = CookieManager.getInstance();
-
-            // Pass cookies to download manager if cookies are enabled.  This is required to download images from websites that require a login.
-            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-            if (cookieManager.acceptCookie()) {
-                // Get the cookies for `imageUrl`.
-                String cookies = cookieManager.getCookie(imageUrl);
-
-                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
-                downloadRequest.addRequestHeader("Cookie", cookies);
-            }
-
-            // Get the dialog.
-            Dialog dialog = dialogFragment.getDialog();
-
-            // Remove the incorrect lint warning below that the dialog might be null.
-            assert dialog != null;
-
-            // Get the file name from the dialog fragment.
-            EditText downloadImageNameEditText = dialog.findViewById(R.id.download_image_name);
-            String imageName = downloadImageNameEditText.getText().toString();
-
-            // Specify the download location.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
-                // Download to the public download directory.
-                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
-            } else {  // External write permission denied.
-                // Download to the app's external download directory.
-                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
-            }
-
-            // Allow `MediaScanner` to index the download if it is a media file.
-            downloadRequest.allowScanningByMediaScanner();
-
-            // Add the URL as the description for the download.
-            downloadRequest.setDescription(imageUrl);
-
-            // Show the download notification after the download is completed.
-            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
-
-            // Remove the lint warning below that `downloadManager` might be `null`.
-            assert downloadManager != null;
-
-            // Initiate the download.
-            downloadManager.enqueue(downloadRequest);
-        } else {  // The image is not an HTTP or HTTPS URI.
-            Snackbar.make(currentWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
-        }
-    }
-
-    @Override
-    public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
-        // Download the file if it has an HTTP or HTTPS URI.
-        if (downloadUrl.startsWith("http")) {
-            // Get a handle for the system `DOWNLOAD_SERVICE`.
-            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
-
-            // Parse `downloadUrl`.
-            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
-
-            // Get a handle for the cookie manager.
-            CookieManager cookieManager = CookieManager.getInstance();
-
-            // Pass cookies to download manager if cookies are enabled.  This is required to download files from websites that require a login.
-            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-            if (cookieManager.acceptCookie()) {
-                // Get the cookies for `downloadUrl`.
-                String cookies = cookieManager.getCookie(downloadUrl);
-
-                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
-                downloadRequest.addRequestHeader("Cookie", cookies);
-            }
-
-            // Get the dialog.
-            Dialog dialog = dialogFragment.getDialog();
-
-            // Remove the incorrect lint warning below that the dialog might be null.
-            assert dialog != null;
-
-            // Get the file name from the dialog.
-            EditText downloadFileNameEditText = dialog.findViewById(R.id.download_file_name);
-            String fileName = downloadFileNameEditText.getText().toString();
-
-            // Specify the download location.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
-                // Download to the public download directory.
-                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
-            } else {  // External write permission denied.
-                // Download to the app's external download directory.
-                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
-            }
-
-            // Allow `MediaScanner` to index the download if it is a media file.
-            downloadRequest.allowScanningByMediaScanner();
-
-            // Add the URL as the description for the download.
-            downloadRequest.setDescription(downloadUrl);
-
-            // Show the download notification after the download is completed.
-            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
-
-            // Remove the lint warning below that `downloadManager` might be `null`.
-            assert downloadManager != null;
-
-            // Initiate the download.
-            downloadManager.enqueue(downloadRequest);
-        } else {  // The download is not an HTTP or HTTPS URI.
-            Snackbar.make(currentWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
-        }
-    }
-
     // Override `onBackPressed` to handle the navigation drawer and and the WebViews.
     @Override
     public void onBackPressed() {
@@ -3088,7 +2940,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Get the font size from the edit text.
         try {
-            newFontSize = Integer.valueOf(fontSizeEditText.getText().toString());
+            newFontSize = Integer.parseInt(fontSizeEditText.getText().toString());
         } catch (Exception exception) {
             // If the edit text does not contain a valid font size do nothing.
         }
@@ -3165,7 +3017,7 @@ 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 StoragePermissionDialog.SAVE:
+                case StoragePermissionDialog.SAVE_URL:
                     // Save the URL.
                     new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
                     break;
@@ -3194,7 +3046,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) {  // The file path is in the external private directory.
                 // Save the webpage according to the save type.
                 switch (saveType) {
-                    case StoragePermissionDialog.SAVE:
+                    case StoragePermissionDialog.SAVE_URL:
                         // Save the URL.
                         new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
                         break;
@@ -3219,18 +3071,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
                 } else {  // Show the permission request directly.
                     switch (saveType) {
-                        case StoragePermissionDialog.SAVE:
+                        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_WEBPAGE_RAW_REQUEST_CODE);
+                            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_WEBPAGE_ARCHIVE_REQUEST_CODE);
+                            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_WEBPAGE_IMAGE_REQUEST_CODE);
+                            ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE);
                             break;
                     }
                 }
@@ -3246,61 +3098,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE);
                 break;
 
-            case StoragePermissionDialog.SAVE:
+            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_WEBPAGE_RAW_REQUEST_CODE);
+                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_WEBPAGE_ARCHIVE_REQUEST_CODE);
+                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_WEBPAGE_IMAGE_REQUEST_CODE);
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_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.
@@ -3315,25 +3132,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 openFilePath = "";
                 break;
 
-            case PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE:
+            case PERMISSION_SAVE_URL_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);
+                    // Save the raw URL.
+                    new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
                 } 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.
+                // Reset the save strings.
+                saveWebpageUrl = "";
                 saveWebpageFilePath = "";
                 break;
 
-            case PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE:
+            case PERMISSION_SAVE_AS_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 image.
-                    new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
+                    // 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();
@@ -3343,18 +3161,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 saveWebpageFilePath = "";
                 break;
 
-            case PERMISSION_SAVE_WEBPAGE_RAW_REQUEST_CODE:
+            case PERMISSION_SAVE_AS_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 raw URL.
-                    new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
+                    // 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 strings.
-                saveWebpageUrl = "";
+                // Reset the save webpage file path.
                 saveWebpageFilePath = "";
                 break;
         }
@@ -4256,7 +4073,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 try {  // Try the specified font size to see if it is valid.
                     if (fontSize == 0) {  // Apply the default font size.
                             // Try to set the font size from the value in the app settings.
-                            nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
+                            nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
                     } else {  // Apply the font size from domain settings.
                         nestedScrollWebView.getSettings().setTextZoom(fontSize);
                     }
@@ -4404,7 +4221,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Apply the default font size setting.
                 try {
                     // Try to set the font size from the value in the app settings.
-                    nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
+                    nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
                 } catch (Exception exception) {
                     // If the app settings value is invalid, set the font size to 100%.
                     nestedScrollWebView.getSettings().setTextZoom(100);
@@ -5392,48 +5209,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Allow the downloading of files.
         nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
-            // Check if the download should be processed by an external app.
-            if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
-                // Create a download intent.  Not specifying the action type will display the maximum number of options.
-                Intent downloadIntent = new Intent();
-
-                // Set the URI and the MIME type.  Specifying `text/html` displays a good number of options.
-                downloadIntent.setDataAndType(Uri.parse(downloadUrl), "text/html");
-
-                // Flag the intent to open in a new task.
-                downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-                // Show the chooser.
-                startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
-            } else {  // Download with Android's download manager.
-                // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
-                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission has not been granted.
-                    // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
-
-                    // Store the variables for future use by `onRequestPermissionsResult()`.
-                    this.downloadUrl = downloadUrl;
-                    downloadContentDisposition = contentDisposition;
-                    downloadContentLength = contentLength;
-
-                    // Show a dialog if the user has previously denied the permission.
-                    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                        // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
-                        DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
-
-                        // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
-                        downloadLocationPermissionDialogFragment.show(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}, PERMISSION_DOWNLOAD_FILE_REQUEST_CODE);
-                    }
-                } else {  // The storage permission has already been granted.
-                    // Get a handle for the download file alert dialog.
-                    DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, contentDisposition, contentLength);
+            // Instantiate the save dialog.
+            DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, downloadUrl, userAgent, nestedScrollWebView.getAcceptFirstPartyCookies());
 
-                    // Show the download file alert dialog.
-                    downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
-                }
-            }
+            // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
+            saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
         });
 
         // Update the find on page count.
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java
new file mode 100644 (file)
index 0000000..fa4d374
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Copyright © 2020 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.asynctasks;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.webkit.CookieManager;
+import android.widget.TextView;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.helpers.ProxyHelper;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.text.NumberFormat;
+
+public class GetUrlSize extends AsyncTask<String, Void, String> {
+    // Define a weak reference for the calling context and fragment.
+    private WeakReference<Context> contextWeakReference;
+    private WeakReference<AlertDialog> alertDialogWeakReference;
+
+    // Define the class variables.
+    private String userAgent;
+    private boolean cookiesEnabled;
+
+    // The public constructor.
+    public GetUrlSize(Context context, AlertDialog alertDialog, String userAgent, boolean cookiesEnabled) {
+        // Populate the week references to the calling activity and fragment.
+        contextWeakReference = new WeakReference<>(context);
+        alertDialogWeakReference = new WeakReference<>(alertDialog);
+
+        // Store the class variables.
+        this.userAgent = userAgent;
+        this.cookiesEnabled = cookiesEnabled;
+    }
+
+    @Override
+    protected String doInBackground(String... urlToSave) {
+        // Get a handle for the context and the fragment.
+        Context context = contextWeakReference.get();
+        AlertDialog alertDialog = alertDialogWeakReference.get();
+
+        // Abort if the fragment is gone.
+        if (alertDialog == null) {
+            return null;
+        }
+
+        // Initialize the formatted file size string.
+        String formattedFileSize = context.getString(R.string.unknown_size);
+
+        // Because everything relating to requesting data from a webserver can throw errors, the entire section much catch `IOExceptions`.
+        try {
+            // Get the URL from the calling fragment.
+            URL url = new URL(urlToSave[0]);
+
+            // Instantiate the proxy helper.
+            ProxyHelper proxyHelper = new ProxyHelper();
+
+            // Get the current proxy.
+            Proxy proxy = proxyHelper.getCurrentProxy(context);
+
+            // Open a connection to the URL.  No data is actually sent at this point.
+            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
+
+            // Add the user agent to the header property.
+            httpURLConnection.setRequestProperty("User-Agent", userAgent);
+
+            // Add the cookies if they are enabled.
+            if (cookiesEnabled) {
+                // Ge the cookies for the current domain.
+                String cookiesString = CookieManager.getInstance().getCookie(url.toString());
+
+                // Only add the cookies if they are not null.
+                if (cookiesString != null) {
+                    // Add the cookies to the header property.
+                    httpURLConnection.setRequestProperty("Cookie", cookiesString);
+                }
+            }
+
+            // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
+            try {
+                // Exit if the task has been cancelled.
+                if (isCancelled()) {
+                    // Disconnect the HTTP URL connection.
+                    httpURLConnection.disconnect();
+
+                    // Return the formatted file size string.
+                    return formattedFileSize;
+                }
+
+                // Get the status code.
+                int responseCode = httpURLConnection.getResponseCode();
+
+                // Exit if the task has been cancelled.
+                if (isCancelled()) {
+                    // Disconnect the HTTP URL connection.
+                    httpURLConnection.disconnect();
+
+                    // Return the formatted file size string.
+                    return formattedFileSize;
+                }
+
+                // Check the response code.
+                if (responseCode >= 400) {  // The response code is an error message.
+                    // Set the formatted file size to indicate a bad URL.
+                    formattedFileSize = context.getString(R.string.bad_url);
+                } else {  // The response code is not an error message.
+                    // Get the content length header.
+                    String contentLengthString = httpURLConnection.getHeaderField("Content-Length");
+
+                    // Define the file size long.
+                    long fileSize;
+
+                    // Make sure the content length isn't null.
+                    if (contentLengthString != null) {  // The content length isn't null.
+                        // Convert the content length to a long.
+                        fileSize = Long.parseLong(contentLengthString);
+
+                        // Format the file size.
+                        formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes);
+                    }
+                }
+            } finally {
+                // Disconnect the HTTP URL connection.
+                httpURLConnection.disconnect();
+            }
+        } catch (IOException exception) {
+            // Set the formatted file size to indicate a bad URL.
+            formattedFileSize = context.getString(R.string.bad_url);
+        }
+
+        // Return the formatted file size string.
+        return formattedFileSize;
+    }
+
+    // `onPostExecute()` operates on the UI thread.
+    @Override
+    protected void onPostExecute(String fileSize) {
+        // Get a handle for the context and alert dialog.
+        AlertDialog alertDialog = alertDialogWeakReference.get();
+
+        // Abort if the alert dialog is gone.
+        if (alertDialog == null) {
+            return;
+        }
+
+        // Get a handle for the file size text view.
+        TextView fileSizeTextView = alertDialog.findViewById(R.id.file_size_textview);
+
+        // Update the file size.
+        fileSizeTextView.setText(fileSize);
+    }
+}
\ No newline at end of file
index 1eb50323f096afdcfb70fabc64e4564636e89588..def7babd913b4032787ff5e1c9777d46bce7c329 100644 (file)
@@ -21,6 +21,8 @@ package com.stoutner.privacybrowser.asynctasks;
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.webkit.CookieManager;
 
@@ -39,9 +41,10 @@ import java.lang.ref.WeakReference;
 import java.net.HttpURLConnection;
 import java.net.Proxy;
 import java.net.URL;
+import java.text.NumberFormat;
 
-public class SaveUrl extends AsyncTask<String, Void, String> {
-    // Define a weak references for the calling context and activities.
+public class SaveUrl extends AsyncTask<String, Long, String> {
+    // Define a weak references for the calling context and activity.
     private WeakReference<Context> contextWeakReference;
     private WeakReference<Activity> activityWeakReference;
 
@@ -81,7 +84,7 @@ public class SaveUrl extends AsyncTask<String, Void, String> {
         NoSwipeViewPager noSwipeViewPager = activity.findViewById(R.id.webviewpager);
 
         // Create a saving file snackbar.
-        savingFileSnackbar = Snackbar.make(noSwipeViewPager, R.string.saving_file, Snackbar.LENGTH_INDEFINITE);
+        savingFileSnackbar = Snackbar.make(noSwipeViewPager, activity.getString(R.string.saving_file) + ":  " + filePathString, Snackbar.LENGTH_INDEFINITE);
 
         // Display the saving file snackbar.
         savingFileSnackbar.show();
@@ -103,7 +106,7 @@ public class SaveUrl extends AsyncTask<String, Void, String> {
 
         // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
         try {
-            // Get the URL from the main activity.
+            // Get the URL from the calling activity.
             URL url = new URL(urlToSave[0]);
 
             // Instantiate the proxy helper.
@@ -132,8 +135,20 @@ public class SaveUrl extends AsyncTask<String, Void, String> {
 
             // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
             try {
-                // Get the response code, which causes the connection to the server to be made.
-                httpUrlConnection.getResponseCode();
+                // Get the content length header, which causes the connection to the server to be made.
+                String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
+
+                // Define the file size long.
+                long fileSize;
+
+                // Make sure the content length isn't null.
+                if (contentLengthString != null) {  // The content length isn't null.
+                    // Convert the content length to an long.
+                    fileSize = Long.parseLong(contentLengthString);
+                } else {  // The content length is null.
+                    // Set the file size to be `-1`.
+                    fileSize = -1;
+                }
 
                 // Get the response body stream.
                 InputStream inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
@@ -157,6 +172,9 @@ public class SaveUrl extends AsyncTask<String, Void, String> {
                 // Initialize the conversion buffer byte array.
                 byte[] conversionBufferByteArray = new byte[1024];
 
+                // Initialize the downloaded kilobytes counter.
+                long downloadedKilobytesCounter = 0;
+
                 // Define the buffer length variable.
                 int bufferLength;
 
@@ -164,6 +182,23 @@ public class SaveUrl extends AsyncTask<String, Void, String> {
                 while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer in > 0.
                     // Write the contents of the conversion buffer to the output stream.
                     outputStream.write(conversionBufferByteArray, 0, bufferLength);
+
+                    // Increment the downloaded kilobytes counter.
+                    downloadedKilobytesCounter++;
+
+                    // Update the file download progress snackbar.
+                    if (fileSize == -1) {  // The file size is unknown.
+                        // Convert the downloaded kilobytes counter to a negative number
+                        long downloadedKilobytes = 0 - downloadedKilobytesCounter;
+
+                        publishProgress(downloadedKilobytes);
+                    } else {  // The file size is known.
+                        // Calculate the download percentage.
+                        long downloadPercentage = (downloadedKilobytesCounter * 1024 * 100) / fileSize;
+
+                        // Update the download percentage.
+                        publishProgress(downloadPercentage);
+                    }
                 }
 
                 // Close the input stream.
@@ -171,6 +206,15 @@ public class SaveUrl extends AsyncTask<String, Void, String> {
 
                 // Close the output stream.
                 outputStream.close();
+
+                // Define a media scanner intent, which adds items like pictures to Android's recent file list.
+                Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+
+                // Add the URI to the media scanner intent.
+                mediaScannerIntent.setData(Uri.fromFile(file));
+
+                // Make it so.
+                activity.sendBroadcast(mediaScannerIntent);
             } finally {
                 // Disconnect the HTTP URL connection.
                 httpUrlConnection.disconnect();
@@ -184,6 +228,33 @@ public class SaveUrl extends AsyncTask<String, Void, String> {
         return saveDisposition;
     }
 
+    // `onProgressUpdate()` operates on the UI thread.
+    @Override
+    protected void onProgressUpdate(Long... downloadPercentage) {
+        // Get a handle for the activity.
+        Activity activity = activityWeakReference.get();
+
+        // Abort if the activity is gone.
+        if ((activity == null) || activity.isFinishing()) {
+            return;
+        }
+
+        // Check to see if a download percentage has been calculated.
+        if (downloadPercentage[0] < 0) {  // There is no download percentage.  The negative number represents the raw downloaded kilobytes.
+            // Calculate the number of bytes downloaded.
+            long numberOfBytesDownloaded = (0 - downloadPercentage[0]) * 1024;
+
+            // Format the number of bytes downloaded.
+            String formattedNumberOfBytesDownloaded = NumberFormat.getInstance().format(numberOfBytesDownloaded);
+
+            // Update the snackbar.
+            savingFileSnackbar.setText(activity.getString(R.string.saving_file) + ":  " + formattedNumberOfBytesDownloaded + " " + activity.getString(R.string.bytes) + " - " + filePathString);
+        } else {  // There is a download percentage.
+            // Update the snackbar.
+            savingFileSnackbar.setText(activity.getString(R.string.saving_file) + ":  " + downloadPercentage[0] + "% - " + filePathString);
+        }
+    }
+
     // `onPostExecute()` operates on the UI thread.
     @Override
     protected void onPostExecute(String saveDisposition) {
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFileDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFileDialog.java
deleted file mode 100644 (file)
index 6ce8477..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright © 2016-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.annotation.SuppressLint;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.DialogFragment;  // The AndroidX dialog fragment must be used or an error is produced on API <=22.
-
-import com.stoutner.privacybrowser.R;
-
-import java.util.Locale;
-
-public class DownloadFileDialog extends DialogFragment {
-    // `downloadFileListener` is used in `onAttach()` and `onCreateDialog()`.
-    private DownloadFileListener downloadFileListener;
-
-    // The public interface is used to send information back to the parent activity.
-    public interface DownloadFileListener {
-        void onDownloadFile(DialogFragment dialogFragment, String downloadUrl);
-    }
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for `DownloadFileListener` from the launching context.
-        downloadFileListener = (DownloadFileListener) context;
-    }
-
-    public static DownloadFileDialog fromUrl(String urlString, String contentDisposition, long contentLength) {
-        // Create an arguments bundle.
-        Bundle argumentsBundle = new Bundle();
-
-        // Create a variable for the file name string.
-        String fileNameString;
-
-        // Get the index of the end of `filename=` from the file name string.
-        int fileNameIndex = contentDisposition.indexOf("filename=") + 9;
-
-        // Parse the filename from `contentDisposition`.
-        if (contentDisposition.contains("filename=\"")) {  // The file name is contained in a string surrounded by `""`.
-            fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=\"") + 10, contentDisposition.indexOf("\"", contentDisposition.indexOf("filename=\"") + 10));
-        } else if (contentDisposition.contains("filename=") && ((contentDisposition.indexOf(";", fileNameIndex)) > 0 )) {
-            // The file name is contained in a string beginning with `filename=` and ending with `;`.
-            fileNameString = contentDisposition.substring(fileNameIndex, contentDisposition.indexOf(";", fileNameIndex));
-        } else if (contentDisposition.contains("filename=")) {  // The file name is contained in a string beginning with `filename=` and proceeding to the end of `contentDisposition`.
-            fileNameString = contentDisposition.substring(fileNameIndex);
-        } else {  // `contentDisposition` does not contain the filename, so use the last path segment of the URL.
-            Uri downloadUri = Uri.parse(urlString);
-            fileNameString = downloadUri.getLastPathSegment();
-        }
-
-        // Store the variables in the bundle.
-        argumentsBundle.putString("URL", urlString);
-        argumentsBundle.putString("File_Name", fileNameString);
-        argumentsBundle.putLong("File_Size", contentLength);
-
-        // Add the arguments bundle to this instance of `DownloadFileDialog`.
-        DownloadFileDialog thisDownloadFileDialog = new DownloadFileDialog();
-        thisDownloadFileDialog.setArguments(argumentsBundle);
-        return thisDownloadFileDialog;
-    }
-
-    @Override
-    @NonNull
-    // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
-    @SuppressLint("InflateParams")
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Remove the warning below that `getArguments()` might be null.
-        assert getArguments() != null;
-
-        // Store the variables from the bundle.
-        String downloadUrl = getArguments().getString("URL");
-        String downloadFileName = getArguments().getString("File_Name");
-        long fileSizeLong = getArguments().getLong("File_Size");
-
-        // Initialize the file size string.
-        String fileSize;
-
-        // Convert `fileSizeLong` to a String.
-        if (fileSizeLong == -1) {  // We don't know the file size.
-            fileSize = getString(R.string.unknown_size);
-        } else {  // Convert `fileSize` to MB and store it in `fileSizeString`.  `%.3g` displays the three most significant digits.
-            fileSize = String.format(Locale.getDefault(), "%.3g", (float) fileSizeLong / 1048576) + " MB";
-        }
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
-
-        // Get the screenshot and theme preferences.
-        boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
-
-        // Use an alert dialog builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder;
-
-        // Set the style according to the theme.
-        if (darkTheme) {
-            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
-        } else {
-            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
-        }
-
-        // Set the title.
-        dialogBuilder.setTitle(R.string.save_as);
-
-        // Set the icon according to the theme.
-        if (darkTheme) {
-            dialogBuilder.setIcon(R.drawable.save_dialog_dark);
-        } else {
-            dialogBuilder.setIcon(R.drawable.save_dialog_light);
-        }
-
-        // Remove the warning below that `getActivity()` might be null;
-        assert getActivity() != null;
-
-        // Set the view.  The parent view is `null` because it will be assigned by `AlertDialog`.
-        dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.download_file_dialog, null));
-
-        // Set an listener on the negative button.
-        dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
-            // Do nothing if `Cancel` is clicked.  The `Dialog` will automatically close.
-        });
-
-        // Set an listener on the positive button
-        dialogBuilder.setPositiveButton(R.string.download, (DialogInterface dialog, int which) -> {
-            // trigger `onDownloadFile()` and return the `DialogFragment` and the download URL to the parent activity.
-            downloadFileListener.onDownloadFile(DownloadFileDialog.this, downloadUrl);
-        });
-
-        // Create an alert dialog from the alert dialog builder`.
-        final AlertDialog alertDialog = dialogBuilder.create();
-
-        // Remove the warning below that `getWindow()` might be null.
-        assert alertDialog.getWindow() != null;
-
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // Show the keyboard when alert dialog is displayed on the screen.
-        alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-
-        // We need to show `alertDialog` before we can modify the contents.
-        alertDialog.show();
-
-        // Set the text for `downloadFileSizeTextView`.
-        TextView downloadFileSizeTextView = alertDialog.findViewById(R.id.download_file_size);
-        downloadFileSizeTextView.setText(fileSize);
-
-        // Set the text for `downloadFileNameTextView`.
-        EditText downloadFileNameTextView = alertDialog.findViewById(R.id.download_file_name);
-        downloadFileNameTextView.setText(downloadFileName);
-
-        // Allow the `enter` key on the keyboard to save the file from `downloadFileNameTextView`.
-        downloadFileNameTextView.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
-            // If the event is an `ACTION_DOWN` on the `enter` key, initiate the download.
-            if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
-                // trigger `onDownloadFile()` and return the `DialogFragment` and the URL to the parent activity.
-                downloadFileListener.onDownloadFile(DownloadFileDialog.this, downloadUrl);
-
-                // Manually dismiss the alert dialog.
-                alertDialog.dismiss();
-
-                // Consume the event.
-                return true;
-            } else {  // If any other key was pressed, do not consume the event.
-                return false;
-            }
-        });
-
-        // `onCreateDialog` requires the return of an `AlertDialog`.
-        return alertDialog;
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImageDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImageDialog.java
deleted file mode 100644 (file)
index 9c1afe6..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright © 2016-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.annotation.SuppressLint;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.EditText;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.DialogFragment;  // The AndroidX dialog fragment must be used or an error is produced on API <=22.
-
-import com.stoutner.privacybrowser.R;
-
-public class DownloadImageDialog extends DialogFragment {
-    // `downloadImageListener` is used in `onAttach()` and `onCreateDialog()`.
-    private DownloadImageListener downloadImageListener;
-
-    // The public interface is used to send information back to the parent activity.
-    public interface DownloadImageListener {
-        void onDownloadImage(DialogFragment dialogFragment, String downloadUrl);
-    }
-
-    // Check to make sure tha the parent activity implements the listener.
-    @Override
-    public void onAttach(@NonNull Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for `DownloadImageListener` from the launching context.
-        downloadImageListener = (DownloadImageListener) context;
-    }
-
-    public static DownloadImageDialog imageUrl(String imageUrlString) {
-        // Create an arguments bundle.
-        Bundle argumentsBundle = new Bundle();
-
-        // Create a variable for the image name string.
-        String imageNameString;
-
-        // Extract the image name string from the image URL.
-        Uri imageUri = Uri.parse(imageUrlString);
-        imageNameString = imageUri.getLastPathSegment();
-
-        // Store the variables in the bundle.
-        argumentsBundle.putString("URL", imageUrlString);
-        argumentsBundle.putString("Image_Name", imageNameString);
-
-        // Add the arguments bundle to this instance of `DownloadFileDialog`.
-        DownloadImageDialog thisDownloadFileDialog = new DownloadImageDialog();
-        thisDownloadFileDialog.setArguments(argumentsBundle);
-        return thisDownloadFileDialog;
-    }
-
-    // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
-    @SuppressLint("InflateParams")
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Remove the warning below that `getArguments()` might be null.
-        assert getArguments() != null;
-
-        // Get the strings from the bundle.
-        String imageUrl = getArguments().getString("URL");
-        String imageFileName = getArguments().getString("Image_Name");
-
-        // Remove the warning below that `.getActivity()` might be null.
-        assert getActivity() != null;
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
-
-        // Get the screenshot and theme preferences.
-        boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
-
-        // Use and alert dialog builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder;
-
-        // Set the style according to the theme.
-        if (darkTheme) {
-            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
-        } else {
-            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
-        }
-
-        // Set the title.
-        dialogBuilder.setTitle(R.string.save_image_as);
-
-        // Set the icon according to the theme.
-        if (darkTheme) {
-            dialogBuilder.setIcon(R.drawable.images_enabled_dark);
-        } else {
-            dialogBuilder.setIcon(R.drawable.images_enabled_light);
-        }
-
-        // Remove the incorrect lint warning below that `getActivity() might be null.
-        assert getActivity() != null;
-
-        // Set the view.  The parent view is `null` because it will be assigned by `AlertDialog`.
-        dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.download_image_dialog, null));
-
-        // Set an listener on the negative button.
-        dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
-            // Do nothing if `Cancel` is clicked.
-        });
-
-        // Set an listener on the positive button
-        dialogBuilder.setPositiveButton(R.string.download, (DialogInterface dialog, int which) -> {
-            // trigger `onDownloadFile()` and return the `DialogFragment` and the download URL to the parent activity.
-            downloadImageListener.onDownloadImage(DownloadImageDialog.this, imageUrl);
-        });
-
-
-        // Create an alert dialog from the alert dialog builder.
-        final AlertDialog alertDialog = dialogBuilder.create();
-
-        // Remove the warning below that `getWindow()` might be null.
-        assert alertDialog.getWindow() != null;
-
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // Show the keyboard when `alertDialog` is displayed on the screen.
-        alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-
-        // The alert dialog must be shown before the contents can be modified.
-        alertDialog.show();
-
-        // Set the text for `downloadImageNameTextView`.
-        EditText downloadImageNameTextView = alertDialog.findViewById(R.id.download_image_name);
-        downloadImageNameTextView.setText(imageFileName);
-
-        // Allow the `enter` key on the keyboard to save the file from `downloadImageNameTextView`.
-        downloadImageNameTextView.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
-            // If the event is an `ACTION_DOWN` on the `enter` key, initiate the download.
-            if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
-                // trigger `onDownloadImage()` and return the `DialogFragment` and the URL to the parent activity.
-                downloadImageListener.onDownloadImage(DownloadImageDialog.this, imageUrl);
-
-                // Manually dismiss the alert dialog.
-                alertDialog.dismiss();
-
-                // Consume the event.
-                return true;
-            } else {  // If any other key was pressed, do not consume the event.
-                return false;
-            }
-        });
-
-        // `onCreateDialog` requires the return of an `AlertDialog`.
-        return alertDialog;
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadLocationPermissionDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadLocationPermissionDialog.java
deleted file mode 100644 (file)
index a8ebd73..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright © 2018-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.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.view.WindowManager;
-
-import com.stoutner.privacybrowser.R;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.DialogFragment;  // The AndroidX dialog fragment must be used or an error is produced on API <=22.
-
-public class DownloadLocationPermissionDialog extends DialogFragment {
-    // The constants are used to differentiate between the two download types.
-    public static final int DOWNLOAD_FILE = 1;
-    public static final int DOWNLOAD_IMAGE = 2;
-
-    // The listener is used in `onAttach()` and `onCreateDialog()`.
-    private DownloadLocationPermissionDialogListener downloadLocationPermissionDialogListener;
-
-    // The public interface is used to send information back to the parent activity.
-    public interface DownloadLocationPermissionDialogListener {
-        void onCloseDownloadLocationPermissionDialog(int downloadType);
-    }
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for the listener from the launching context.
-        downloadLocationPermissionDialogListener = (DownloadLocationPermissionDialogListener) context;
-    }
-
-    public static DownloadLocationPermissionDialog downloadType(int type) {
-        // Create an arguments bundle.
-        Bundle argumentsBundle = new Bundle();
-
-        // Store the download type in the bundle.
-        argumentsBundle.putInt("download_type", type);
-
-        // Add the arguments bundle to this instance of the dialog.
-        DownloadLocationPermissionDialog thisDownloadLocationPermissionDialog = new DownloadLocationPermissionDialog();
-        thisDownloadLocationPermissionDialog.setArguments(argumentsBundle);
-        return thisDownloadLocationPermissionDialog;
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Remove the incorrect lint warning below that `getArguments().getInt()` might be null.
-        assert getArguments() != null;
-
-        // Store the download type in a local variable.
-        int downloadType = getArguments().getInt("download_type");
-
-        // Use a builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder;
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
-
-        // Get the screenshot and theme preferences.
-        boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-        boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
-
-        // Set the style and the icon according to the theme.
-        if (darkTheme) {
-            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
-            dialogBuilder.setIcon(R.drawable.downloads_dark);
-        } else {
-            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
-            dialogBuilder.setIcon(R.drawable.downloads_light);
-        }
-
-        // Set the title.
-        dialogBuilder.setTitle(R.string.download_location);
-
-        // Set the text.
-        dialogBuilder.setMessage(R.string.download_location_message);
-
-        // Set an `onClick` listener on the negative button.
-        dialogBuilder.setNegativeButton(R.string.ok, (DialogInterface dialog, int which) -> {
-            // Inform the parent activity that the dialog was closed.
-            downloadLocationPermissionDialogListener.onCloseDownloadLocationPermissionDialog(downloadType);
-        });
-
-        // Create an alert dialog from the builder.
-        final AlertDialog alertDialog = dialogBuilder.create();
-
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            // Remove the warning below that `getWindow()` might be null.
-            assert alertDialog.getWindow() != null;
-
-            // Disable screenshots.
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // `onCreateDialog()` requires the return of an `AlertDialog`.
-        return alertDialog;
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java
new file mode 100644 (file)
index 0000000..d271b87
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * Copyright © 2019-2020 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.net.Uri;
+import android.os.AsyncTask;
+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 androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.PreferenceManager;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.activities.MainWebViewActivity;
+import com.stoutner.privacybrowser.asynctasks.GetUrlSize;
+
+import java.io.File;
+
+public class SaveDialog extends DialogFragment {
+    // Define the save webpage listener.
+    private SaveWebpageListener saveWebpageListener;
+
+    // The public interface is used to send information back to the parent activity.
+    public interface SaveWebpageListener {
+        void onSaveWebpage(int saveType, DialogFragment dialogFragment);
+    }
+
+    // Define the get URL size AsyncTask.  This allows previous instances of the task to be cancelled if a new one is run.
+    private AsyncTask getUrlSize;
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        // Run the default commands.
+        super.onAttach(context);
+
+        // Get a handle for the save webpage listener from the launching context.
+        saveWebpageListener = (SaveWebpageListener) context;
+    }
+
+    public static SaveDialog saveUrl(int saveType, String url, String userAgent, boolean cookiesEnabled) {
+        // Create an arguments bundle.
+        Bundle argumentsBundle = new Bundle();
+
+        // Store the arguments in the bundle.
+        argumentsBundle.putInt("save_type", saveType);
+        argumentsBundle.putString("url", url);
+        argumentsBundle.putString("user_agent", userAgent);
+        argumentsBundle.putBoolean("cookies_enabled", cookiesEnabled);
+
+        // Create a new instance of the save webpage dialog.
+        SaveDialog saveWebpageDialog = new SaveDialog();
+
+        // Add the arguments bundle to the new dialog.
+        saveWebpageDialog.setArguments(argumentsBundle);
+
+        // Return the new dialog.
+        return saveWebpageDialog;
+    }
+
+    // `@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 arguments.
+        Bundle arguments = getArguments();
+
+        // Remove the incorrect lint warning that the arguments might be null.
+        assert arguments != null;
+
+        // Get the arguments from the bundle.
+        int saveType = arguments.getInt("save_type");
+        String url = arguments.getString("url");
+        String userAgent = arguments.getString("user_agent");
+        boolean cookiesEnabled = arguments.getBoolean("cookies_enabled");
+
+        // 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 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 according to the save type.
+            switch (saveType) {
+                case StoragePermissionDialog.SAVE_URL:
+                    dialogBuilder.setIcon(R.drawable.copy_enabled_dark);
+                    break;
+
+                case StoragePermissionDialog.SAVE_AS_ARCHIVE:
+                    dialogBuilder.setIcon(R.drawable.dom_storage_cleared_dark);
+                    break;
+
+                case StoragePermissionDialog.SAVE_AS_IMAGE:
+                    dialogBuilder.setIcon(R.drawable.images_enabled_dark);
+                    break;
+            }
+        } else {
+            // Set the style.
+            dialogBuilder = new AlertDialog.Builder(activity, R.style.PrivacyBrowserAlertDialogLight);
+
+            // Set the icon according to the save type.
+            switch (saveType) {
+                case StoragePermissionDialog.SAVE_URL:
+                    dialogBuilder.setIcon(R.drawable.copy_enabled_light);
+                    break;
+
+                case StoragePermissionDialog.SAVE_AS_ARCHIVE:
+                    dialogBuilder.setIcon(R.drawable.dom_storage_cleared_light);
+                    break;
+
+                case StoragePermissionDialog.SAVE_AS_IMAGE:
+                    dialogBuilder.setIcon(R.drawable.images_enabled_light);
+                    break;
+            }
+        }
+
+        // Set the title according to the type.
+        switch (saveType) {
+            case StoragePermissionDialog.SAVE_URL:
+                dialogBuilder.setTitle(R.string.save);
+                break;
+
+            case StoragePermissionDialog.SAVE_AS_ARCHIVE:
+                dialogBuilder.setTitle(R.string.save_archive);
+                break;
+
+            case StoragePermissionDialog.SAVE_AS_IMAGE:
+                dialogBuilder.setTitle(R.string.save_image);
+                break;
+        }
+
+        // Set the view.  The parent view is null because it will be assigned by the alert dialog.
+        dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_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 save button listener.
+        dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> {
+            // Return the dialog fragment to the parent activity.
+            saveWebpageListener.onSaveWebpage(saveType, 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 urlEditText = alertDialog.findViewById(R.id.url_edittext);
+        EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext);
+        Button browseButton = alertDialog.findViewById(R.id.browse_button);
+        TextView fileSizeTextView = alertDialog.findViewById(R.id.file_size_textview);
+        TextView fileExistsWarningTextView = alertDialog.findViewById(R.id.file_exists_warning_textview);
+        TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview);
+        Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+        // Update the status of the save button whe the URL changes.
+        urlEditText.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) {
+                // Cancel the get URL size AsyncTask if it is running.
+                if ((getUrlSize != null)) {
+                    getUrlSize.cancel(true);
+                }
+
+                // Get the current URL to save.
+                String urlToSave = urlEditText.getText().toString();
+
+                // Wipe the file size text view.
+                fileSizeTextView.setText("");
+
+                // Get the file size for the current URL.
+                getUrlSize = new GetUrlSize(context, alertDialog, userAgent, cookiesEnabled).execute(urlToSave);
+
+                // Enable the save button if the URL and file name are populated.
+                saveButton.setEnabled(!urlToSave.isEmpty() && !fileNameEditText.getText().toString().isEmpty());
+            }
+        });
+
+        // Update the status of the save button when the file name changes.
+        fileNameEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // Do nothing.
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                // Get the current file name.
+                String fileNameString = fileNameEditText.getText().toString();
+
+                // Convert the file name string to a file.
+                File file = new File(fileNameString);
+
+                // Check to see if the file exists.
+                if (file.exists()) {
+                    // Show the file exists warning.
+                    fileExistsWarningTextView.setVisibility(View.VISIBLE);
+                } else {
+                    // Hide the file exists warning.
+                    fileExistsWarningTextView.setVisibility(View.GONE);
+                }
+
+                // Enable the save button if the file name is populated.
+                saveButton.setEnabled(!fileNameString.isEmpty() && !urlEditText.getText().toString().isEmpty());
+            }
+        });
+
+        // Create a file name string.
+        String fileName = "";
+
+        // Set the file name according to the type.
+        switch (saveType) {
+            case StoragePermissionDialog.SAVE_URL:
+                // Convert the URL to a URI.
+                Uri uri = Uri.parse(url);
+
+                // Get the last path segment.
+                String lastPathSegment = uri.getLastPathSegment();
+
+                // Use a default file name if the last path segment is null.
+                if (lastPathSegment == null) {
+                    lastPathSegment = getString(R.string.file);
+                }
+
+                // Use the last path segment as the file name.
+                fileName = lastPathSegment;
+                break;
+
+            case StoragePermissionDialog.SAVE_AS_ARCHIVE:
+                fileName = getString(R.string.webpage_mht);
+                break;
+
+            case StoragePermissionDialog.SAVE_AS_IMAGE:
+                fileName = getString(R.string.webpage_png);
+                break;
+        }
+
+        // Save the file name as the default file name.  This must be final to be used in the lambda below.
+        final String defaultFileName = fileName;
+
+        // 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() + "/" + defaultFileName;
+
+            // Hide the storage permission text view.
+            storagePermissionTextView.setVisibility(View.GONE);
+        } else {  // The storage permission has not been granted.
+            // Set the default file path to use the external private directory.
+            defaultFilePath = context.getExternalFilesDir(null) + "/" + defaultFileName;
+        }
+
+        // Populate the edit texts.
+        urlEditText.setText(url);
+        fileNameEditText.setText(defaultFilePath);
+
+        // Move the cursor to the end of the default file path.
+        fileNameEditText.setSelection(defaultFilePath.length());
+
+        // Handle clicks on the browse button.
+        browseButton.setOnClickListener((View view) -> {
+            // Create the file picker intent.
+            Intent browseIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+            // Set the intent MIME type to include all files so that everything is visible.
+            browseIntent.setType("*/*");
+
+            // Set the initial file name according to the type.
+            browseIntent.putExtra(Intent.EXTRA_TITLE, defaultFileName);
+
+            // Set the initial directory if the minimum API >= 26.
+            if (Build.VERSION.SDK_INT >= 26) {
+                browseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
+            }
+
+            // Request a file that can be opened.
+            browseIntent.addCategory(Intent.CATEGORY_OPENABLE);
+
+            // Start the file picker.  This must be started under `activity` so that the request code is returned correctly.
+            activity.startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_SAVE_WEBPAGE_REQUEST_CODE);
+        });
+
+        // Return the alert dialog.
+        return alertDialog;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java
deleted file mode 100644 (file)
index c9e691f..0000000
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright © 2019-2020 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.net.Uri;
-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 androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.DialogFragment;
-import androidx.preference.PreferenceManager;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.activities.MainWebViewActivity;
-
-import java.io.File;
-
-public class SaveWebpageDialog extends DialogFragment {
-    // Define the save webpage listener.
-    private SaveWebpageListener saveWebpageListener;
-
-    // The public interface is used to send information back to the parent activity.
-    public interface SaveWebpageListener {
-        void onSaveWebpage(int saveType, DialogFragment dialogFragment);
-    }
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for the save webpage listener from the launching context.
-        saveWebpageListener = (SaveWebpageListener) context;
-    }
-
-    public static SaveWebpageDialog saveUrl(int saveType, String url) {
-        // Create an arguments bundle.
-        Bundle argumentsBundle = new Bundle();
-
-        // Store the arguments in the bundle.
-        argumentsBundle.putInt("save_type", saveType);
-        argumentsBundle.putString("url", url);
-
-        // Create a new instance of the save webpage dialog.
-        SaveWebpageDialog saveWebpageDialog = new SaveWebpageDialog();
-
-        // Add the arguments bundle to the new dialog.
-        saveWebpageDialog.setArguments(argumentsBundle);
-
-        // Return the new dialog.
-        return saveWebpageDialog;
-    }
-
-    // `@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 arguments.
-        Bundle arguments = getArguments();
-
-        // Remove the incorrect lint warning that the arguments might be null.
-        assert arguments != null;
-
-        // Get the arguments from the bundle.
-        int saveType = arguments.getInt("save_type");
-        String url = arguments.getString("url");
-
-        // 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 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 according to the save type.
-            switch (saveType) {
-                case StoragePermissionDialog.SAVE:
-                    dialogBuilder.setIcon(R.drawable.copy_enabled_dark);
-                    break;
-
-                case StoragePermissionDialog.SAVE_AS_ARCHIVE:
-                    dialogBuilder.setIcon(R.drawable.dom_storage_cleared_dark);
-                    break;
-
-                case StoragePermissionDialog.SAVE_AS_IMAGE:
-                    dialogBuilder.setIcon(R.drawable.images_enabled_dark);
-                    break;
-            }
-        } else {
-            // Set the style.
-            dialogBuilder = new AlertDialog.Builder(activity, R.style.PrivacyBrowserAlertDialogLight);
-
-            // Set the icon according to the save type.
-            switch (saveType) {
-                case StoragePermissionDialog.SAVE:
-                    dialogBuilder.setIcon(R.drawable.copy_enabled_light);
-                    break;
-
-                case StoragePermissionDialog.SAVE_AS_ARCHIVE:
-                    dialogBuilder.setIcon(R.drawable.dom_storage_cleared_light);
-                    break;
-
-                case StoragePermissionDialog.SAVE_AS_IMAGE:
-                    dialogBuilder.setIcon(R.drawable.images_enabled_light);
-                    break;
-            }
-        }
-
-        // Set the title according to the type.
-        switch (saveType) {
-            case StoragePermissionDialog.SAVE:
-                dialogBuilder.setTitle(R.string.save);
-                break;
-
-            case StoragePermissionDialog.SAVE_AS_ARCHIVE:
-                dialogBuilder.setTitle(R.string.save_archive);
-                break;
-
-            case StoragePermissionDialog.SAVE_AS_IMAGE:
-                dialogBuilder.setTitle(R.string.save_image);
-                break;
-        }
-
-        // Set the view.  The parent view is null because it will be assigned by the alert dialog.
-        dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_webpage_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 save button listener.
-        dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> {
-            // Return the dialog fragment to the parent activity.
-            saveWebpageListener.onSaveWebpage(saveType, 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 urlEditText = alertDialog.findViewById(R.id.url_edittext);
-        EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext);
-        Button browseButton = alertDialog.findViewById(R.id.browse_button);
-        TextView fileExistsWarningTextView = alertDialog.findViewById(R.id.file_exists_warning_textview);
-        TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview);
-        Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-
-        // Update the status of the save button whe the URL changes.
-        urlEditText.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 save button if the URL and file name are populated.
-                saveButton.setEnabled(!urlEditText.getText().toString().isEmpty() && !fileNameEditText.getText().toString().isEmpty());
-            }
-        });
-
-        // Update the status of the save button when the file name changes.
-        fileNameEditText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-                // Do nothing.
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                // Do nothing.
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-                // Get the current file name.
-                String fileNameString = fileNameEditText.getText().toString();
-
-                // Convert the file name string to a file.
-                File file = new File(fileNameString);
-
-                // Check to see if the file exists.
-                if (file.exists()) {
-                    // Show the file exists warning.
-                    fileExistsWarningTextView.setVisibility(View.VISIBLE);
-                } else {
-                    // Hide the file exists warning.
-                    fileExistsWarningTextView.setVisibility(View.GONE);
-                }
-
-                // Enable the save button if the file name is populated.
-                saveButton.setEnabled(!fileNameString.isEmpty() && !urlEditText.getText().toString().isEmpty());
-            }
-        });
-
-        // Create a file name string.
-        String fileName = "";
-
-        // Set the file name according to the type.
-        switch (saveType) {
-            case StoragePermissionDialog.SAVE:
-                // Convert the URL to a URI.
-                Uri uri = Uri.parse(url);
-
-                // Get the last path segment.
-                String lastPathSegment = uri.getLastPathSegment();
-
-                // Use a default file name if the last path segment is null.
-                if (lastPathSegment == null) {
-                    lastPathSegment = getString(R.string.file);
-                }
-
-                // Use the last path segment as the file name.
-                fileName = lastPathSegment;
-                break;
-
-            case StoragePermissionDialog.SAVE_AS_ARCHIVE:
-                fileName = getString(R.string.webpage_mht);
-                break;
-
-            case StoragePermissionDialog.SAVE_AS_IMAGE:
-                fileName = getString(R.string.webpage_png);
-                break;
-        }
-
-        // Save the file name as the default file name.  This must be final to be used in the lambda below.
-        final String defaultFileName = fileName;
-
-        // 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() + "/" + defaultFileName;
-
-            // Hide the storage permission text view.
-            storagePermissionTextView.setVisibility(View.GONE);
-        } else {  // The storage permission has not been granted.
-            // Set the default file path to use the external private directory.
-            defaultFilePath = context.getExternalFilesDir(null) + "/" + defaultFileName;
-        }
-
-        // Populate the edit texts.
-        urlEditText.setText(url);
-        fileNameEditText.setText(defaultFilePath);
-
-        // Move the cursor to the end of the default file path.
-        fileNameEditText.setSelection(defaultFilePath.length());
-
-        // Handle clicks on the browse button.
-        browseButton.setOnClickListener((View view) -> {
-            // Create the file picker intent.
-            Intent browseIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-
-            // Set the intent MIME type to include all files so that everything is visible.
-            browseIntent.setType("*/*");
-
-            // Set the initial file name according to the type.
-            browseIntent.putExtra(Intent.EXTRA_TITLE, defaultFileName);
-
-            // Set the initial directory if the minimum API >= 26.
-            if (Build.VERSION.SDK_INT >= 26) {
-                browseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
-            }
-
-            // Request a file that can be opened.
-            browseIntent.addCategory(Intent.CATEGORY_OPENABLE);
-
-            // Start the file picker.  This must be started under `activity` so that the request code is returned correctly.
-            activity.startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_SAVE_WEBPAGE_REQUEST_CODE);
-        });
-
-        // Return the alert dialog.
-        return alertDialog;
-    }
-}
\ No newline at end of file
index 5b171548f2fdefa8b5d8abf33ae37bd8f98e76fe..4743f9be21ce88ea0b5c5ec9d9cbe207623b0c9e 100644 (file)
@@ -36,7 +36,7 @@ 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 = 1;
+    public static final int SAVE_URL = 1;
     public static final int SAVE_AS_ARCHIVE = 2;
     public static final int SAVE_AS_IMAGE = 3;
 
index 16ca2998ca88e379359534641808ae4f40bf4e3d..1d4dd164e4c3fa62810434bc6b23c2eb587332f8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -100,7 +100,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
         Preference swipeToRefreshPreference = findPreference("swipe_to_refresh");
         Preference scrollAppBarPreference = findPreference("scroll_app_bar");
         Preference displayAdditionalAppBarIconsPreference = findPreference("display_additional_app_bar_icons");
-        Preference downloadWithExternalAppPreference = findPreference("download_with_external_app");
         Preference darkThemePreference = findPreference("dark_theme");
         Preference nightModePreference = findPreference("night_mode");
         Preference wideViewportPreference = findPreference("wide_viewport");
@@ -144,7 +143,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
         assert swipeToRefreshPreference != null;
         assert scrollAppBarPreference != null;
         assert displayAdditionalAppBarIconsPreference != null;
-        assert downloadWithExternalAppPreference != null;
         assert darkThemePreference != null;
         assert nightModePreference != null;
         assert wideViewportPreference != null;
@@ -787,21 +785,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
             }
         }
 
-        // Set the download with external app preference icon.
-        if (savedPreferences.getBoolean("download_with_external_app", false)) {
-            if (darkTheme) {
-                downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_enabled_dark);
-            } else {
-                downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_enabled_light);
-            }
-        } else {
-            if (darkTheme) {
-                downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_disabled_dark);
-            } else {
-                downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_disabled_light);
-            }
-        }
-
         // Set the dark theme preference icon.
         if (savedPreferences.getBoolean("dark_theme", false)) {
             darkThemePreference.setIcon(R.drawable.theme_dark);
@@ -1685,23 +1668,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
                     }
                     break;
 
-                case "download_with_external_app":
-                    // Update the icon.
-                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {
-                        if (darkTheme) {
-                            downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_enabled_dark);
-                        } else {
-                            downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_enabled_light);
-                        }
-                    } else {
-                        if (darkTheme) {
-                            downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_disabled_dark);
-                        } else {
-                            downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_disabled_light);
-                        }
-                    }
-                    break;
-
                 case "dark_theme":
                     // Update the icon.
                     if (sharedPreferences.getBoolean("dark_theme", false)) {
index f504329b41d4aeb75390619bfbf620728679ca6d..50a5ad2341f31653f13e3abfac7d68d37851aef0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018-2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2018-2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -39,7 +39,7 @@ public class ImportExportDatabaseHelper {
     public static final String EXPORT_SUCCESSFUL = "Export Successful";
     public static final String IMPORT_SUCCESSFUL = "Import Successful";
 
-    private static final int SCHEMA_VERSION = 9;
+    private static final int SCHEMA_VERSION = 10;
     private static final String PREFERENCES_TABLE = "preferences";
 
     // The preferences constants.
@@ -81,7 +81,6 @@ public class ImportExportDatabaseHelper {
     private static final String SWIPE_TO_REFRESH = "swipe_to_refresh";
     private static final String SCROLL_APP_BAR = "scroll_app_bar";
     private static final String DISPLAY_ADDITIONAL_APP_BAR_ICONS = "display_additional_app_bar_icons";
-    private static final String DOWNLOAD_WITH_EXTERNAL_APP = "download_with_external_app";
     private static final String DARK_THEME = "dark_theme";
     private static final String NIGHT_MODE = "night_mode";
     private static final String WIDE_VIEWPORT = "wide_viewport";
@@ -235,7 +234,6 @@ public class ImportExportDatabaseHelper {
                     SWIPE_TO_REFRESH + " BOOLEAN, " +
                     SCROLL_APP_BAR + " BOOLEAN, " +
                     DISPLAY_ADDITIONAL_APP_BAR_ICONS + " BOOLEAN, " +
-                    DOWNLOAD_WITH_EXTERNAL_APP + " BOOLEAN, " +
                     DARK_THEME + " BOOLEAN, " +
                     NIGHT_MODE + " BOOLEAN, " +
                     WIDE_VIEWPORT + " BOOLEAN, " +
@@ -286,7 +284,6 @@ public class ImportExportDatabaseHelper {
             preferencesContentValues.put(SWIPE_TO_REFRESH, sharedPreferences.getBoolean(SWIPE_TO_REFRESH, true));
             preferencesContentValues.put(SCROLL_APP_BAR, sharedPreferences.getBoolean(SCROLL_APP_BAR, true));
             preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, false));
-            preferencesContentValues.put(DOWNLOAD_WITH_EXTERNAL_APP, sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false));
             preferencesContentValues.put(DARK_THEME, sharedPreferences.getBoolean(DARK_THEME, false));
             preferencesContentValues.put(NIGHT_MODE, sharedPreferences.getBoolean(NIGHT_MODE, false));
             preferencesContentValues.put(WIDE_VIEWPORT, sharedPreferences.getBoolean(WIDE_VIEWPORT, true));
@@ -369,18 +366,7 @@ public class ImportExportDatabaseHelper {
                 switch (importDatabaseVersion){
                     // Upgrade from schema version 1.
                     case 1:
-                        // Add the download with external app column to the preferences table.
-                        importDatabase.execSQL("ALTER TABLE " + PREFERENCES_TABLE + " ADD COLUMN " + DOWNLOAD_WITH_EXTERNAL_APP + " BOOLEAN");
-
-                        // Get the current setting for downloading with an external app.
-                        boolean downloadWithExternalApp = sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false);
-
-                        // Set the download with external app preference to the current value.
-                        if (downloadWithExternalApp) {
-                            importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + DOWNLOAD_WITH_EXTERNAL_APP + " = " + 1);
-                        } else {
-                            importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + DOWNLOAD_WITH_EXTERNAL_APP + " = " + 0);
-                        }
+                        // Previously this upgrade added `download_with_external_app` to the Preferences table.  But that is now removed in schema version 10.
 
                     // Upgrade from schema version 2.
                     case 2:
@@ -534,6 +520,10 @@ public class ImportExportDatabaseHelper {
                         // Populate the table with the current proxy values.
                         importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + PROXY + " = '" + proxy + "'");
                         importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + PROXY_CUSTOM_URL + " = '" + proxyCustomUrl + "'");
+
+                    // Upgrade from schema version 9.
+                    case 9:
+                        // `download_with_external_app` was removed the the Preferences table in schema version 10, but there is no need to make any modifications to the database.
                 }
             }
 
@@ -687,7 +677,6 @@ public class ImportExportDatabaseHelper {
                     .putBoolean(SWIPE_TO_REFRESH, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(SWIPE_TO_REFRESH)) == 1)
                     .putBoolean(SCROLL_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(SCROLL_APP_BAR)) == 1)
                     .putBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DISPLAY_ADDITIONAL_APP_BAR_ICONS)) == 1)
-                    .putBoolean(DOWNLOAD_WITH_EXTERNAL_APP, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DOWNLOAD_WITH_EXTERNAL_APP)) == 1)
                     .putBoolean(DARK_THEME, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DARK_THEME)) == 1)
                     .putBoolean(NIGHT_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(NIGHT_MODE)) == 1)
                     .putBoolean(WIDE_VIEWPORT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(WIDE_VIEWPORT)) == 1)
index 4813364f183eebf46bcbe391dc7982e65c1abbe7..acc4180d7f89fe9d6238e4bdd7990e08d2fae519 100644 (file)
@@ -1,4 +1,4 @@
-<!-- `about_dark.xml` comes from the Android Material icon set, where it is called `info_outline`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `info_outline`.  It is released under the Apache License 2.0. -->
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
index 315f38e312c6d576d5f1a9e186c00174b3f516bf..401cdcb6997dab3d5753e0a2a5fa9db5e0447857 100644 (file)
@@ -1,4 +1,4 @@
-<!-- `about_light.xml` comes from the Android Material icon set, where it is called `info_outline`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `info_outline`.  It is released under the Apache License 2.0. -->
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
index 8730dc4fff2824b582c91d007a4e06d2281c1dbe..15485cbce0f205b417144b79f279c71ec1348ed9 100644 (file)
@@ -1,4 +1,4 @@
-<!-- `add_dark.xml` comes from the Android Material icon set, where it is called `add`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `add`.  It is released under the Apache License 2.0. -->
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
index 97bf1562baab0e9ac3acf2804b964141b71f9bf3..5ecf8d39a8f1eb1c2880fb27968bf1c446df7d32 100644 (file)
@@ -1,4 +1,4 @@
-<!-- `add_light.xml` comes from the Android Material icon set, where it is called `add`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `add`.  It is released under the Apache License 2.0. -->
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
diff --git a/app/src/main/res/drawable/clear_and_exit.xml b/app/src/main/res/drawable/clear_and_exit.xml
new file mode 100644 (file)
index 0000000..2894a26
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- This file is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
+    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF1565C0"
+        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
+        android:strokeWidth="0.95815897" />
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF1565C0"
+        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
+        android:strokeWidth="0.95815897" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/downloads.xml b/app/src/main/res/drawable/downloads.xml
new file mode 100644 (file)
index 0000000..4bbd96e
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- This file comes from the Android Material icon set, where it is called `file_download`.  It is released under the Apache License 2.0. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0" >
+
+    <!-- The hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/downloads_dark.xml b/app/src/main/res/drawable/downloads_dark.xml
deleted file mode 100644 (file)
index 90dead9..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<!-- `downloads_dark.xml` comes from the Android Material icon set, where it is called `file_download`.  It is released under the Apache License 2.0. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
-    android:width="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0" >
-
-    <!-- The hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FFE0E0E0"
-        android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
-</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/downloads_light.xml b/app/src/main/res/drawable/downloads_light.xml
deleted file mode 100644 (file)
index 7d034cc..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<!-- `downloads_light.xml` comes from the Android Material icon set, where it is called `file_download`.  It is released under the Apache License 2.0. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
-    android:width="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0" >
-
-    <!-- The hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
-</vector>
\ No newline at end of file
index b24c439f767ab014bb9c0c1c2e12f0a7860354ba..86bf0683b152cf9901459d9f6e763db677363d63 100644 (file)
@@ -1,4 +1,4 @@
-<!-- `home_enabled_dark.xml` comes from the Android Material icon set, where it is called `home`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `home`.  It is released under the Apache License 2.0. -->
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
index bf95fd68e91168ab3dae3fcffb5352e4adf6704c..9ea9ca8439135721b683b035110a57cfd738d704 100644 (file)
@@ -1,4 +1,4 @@
-<!-- `home_enabled_light.xml` comes from the Android Material icon set, where it is called `home`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `home`.  It is released under the Apache License 2.0. -->
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
diff --git a/app/src/main/res/drawable/home_ghosted_dark.xml b/app/src/main/res/drawable/home_ghosted_dark.xml
deleted file mode 100644 (file)
index b075d29..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<!-- `home_ghosted_dark.xml` comes from the Android Material icon set, where it is called `home`.  It is released under the Apache License 2.0. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
-    android:width="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF616161"
-        android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
-</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/home_ghosted_light.xml b/app/src/main/res/drawable/home_ghosted_light.xml
deleted file mode 100644 (file)
index 9397557..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<!-- `home_ghosted_light.xml` comes from the Android Material icon set, where it is called `home`.  It is released under the Apache License 2.0. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
-    android:width="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FFB7B7B7"
-        android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
-</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/open_with_external_app_disabled_dark.xml b/app/src/main/res/drawable/open_with_external_app_disabled_dark.xml
deleted file mode 100644 (file)
index 478ad68..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- `open_with_external_app_disabled_dark.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
-    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
-
-<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    android:width="24dp"
-    android:autoMirrored="true"
-    tools:ignore="VectorRaster" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF9E9E9E"
-        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
-        android:strokeWidth="0.95815897" />
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF9E9E9E"
-        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
-        android:strokeWidth="0.95815897" />
-</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/open_with_external_app_disabled_light.xml b/app/src/main/res/drawable/open_with_external_app_disabled_light.xml
deleted file mode 100644 (file)
index 24af6a5..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- `open_with_external_app_disabled_light.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
-    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
-
-<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    android:width="24dp"
-    android:autoMirrored="true"
-    tools:ignore="VectorRaster" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF757575"
-        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
-        android:strokeWidth="0.95815897" />
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF757575"
-        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
-        android:strokeWidth="0.95815897" />
-</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/open_with_external_app_enabled_dark.xml b/app/src/main/res/drawable/open_with_external_app_enabled_dark.xml
deleted file mode 100644 (file)
index c07cc72..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- `open_with_external_app_enabled_dark.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
-    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
-
-<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    android:width="24dp"
-    android:autoMirrored="true"
-    tools:ignore="VectorRaster" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF1E88E5"
-        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
-        android:strokeWidth="0.95815897" />
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF1E88E5"
-        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
-        android:strokeWidth="0.95815897" />
-</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/open_with_external_app_enabled_light.xml b/app/src/main/res/drawable/open_with_external_app_enabled_light.xml
deleted file mode 100644 (file)
index eda4a6e..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- `open_with_external_app_enabled_light.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
-    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
-
-<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    android:width="24dp"
-    android:autoMirrored="true"
-    tools:ignore="VectorRaster" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF1565C0"
-        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
-        android:strokeWidth="0.95815897" />
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF1565C0"
-        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
-        android:strokeWidth="0.95815897" />
-</vector>
\ No newline at end of file
index 163583e1a1b291d1ec069cbbc51f123ca4f46b67..150202a5e12c2f3b25563d77f21ce37f9ac8b9d9 100644 (file)
@@ -1,4 +1,4 @@
-<!-- `search_enabled_dark.xml` comes from the Android Material icon set, where it is called `search`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `search`.  It is released under the Apache License 2.0. -->
 
 <!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
 <vector
index 5372f4c6599948a0ccb9d44f86143f48eca78b5a..a7eb2e7a6b1f0a7cbc5d01981d3cb64dca5c1d0d 100644 (file)
@@ -1,4 +1,4 @@
-<!-- `search_enabled_light.xml` comes from the Android Material icon set, where it is called `search`.  It is released under the Apache License 2.0. -->
+<!-- This file comes from the Android Material icon set, where it is called `search`.  It is released under the Apache License 2.0. -->
 
 <!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
 <vector
diff --git a/app/src/main/res/drawable/search_ghosted_dark.xml b/app/src/main/res/drawable/search_ghosted_dark.xml
deleted file mode 100644 (file)
index d83ecbb..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<!-- `search_ghosted_dark.xml` comes from the Android Material icon set, where it is called `search`.  It is released under the Apache License 2.0. -->
-
-<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:autoMirrored="true"
-    android:height="24dp"
-    android:width="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    tools:ignore="VectorRaster" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF616161"
-        android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
-</vector>
diff --git a/app/src/main/res/drawable/search_ghosted_light.xml b/app/src/main/res/drawable/search_ghosted_light.xml
deleted file mode 100644 (file)
index 760eb63..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<!-- `search_ghosted_light.xml` comes from the Android Material icon set, where it is called `search`.  It is released under the Apache License 2.0. -->
-
-<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:autoMirrored="true"
-    android:height="24dp"
-    android:width="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    tools:ignore="VectorRaster" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FFB7B7B7"
-        android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
-</vector>
diff --git a/app/src/main/res/layout/download_file_dialog.xml b/app/src/main/res/layout/download_file_dialog.xml
deleted file mode 100644 (file)
index d3812fc..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2016-2017,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/>. -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_height="wrap_content"
-    android:layout_width="match_parent"
-    android:padding="6dp">
-
-
-    <!-- The `TextInputLayout` makes the `android:hint` float above the `EditText`. -->
-    <com.google.android.material.textfield.TextInputLayout
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent" >
-
-        <!-- `android:imeOptions="actionsGo"` sets the keyboard to have a `go` key instead of a `new line` key.  `android:inputType="textUri"` disables spell check in the `EditText`. -->
-        <com.google.android.material.textfield.TextInputEditText
-            android:id="@+id/download_file_name"
-            android:layout_height="wrap_content"
-            android:layout_width="match_parent"
-            android:hint="@string/file_name"
-            android:imeOptions="actionGo"
-            android:inputType="textUri" />
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <TextView
-        android:id="@+id/download_file_size"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_marginEnd="3dp"
-        android:layout_gravity="end" />
-</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/download_image_dialog.xml b/app/src/main/res/layout/download_image_dialog.xml
deleted file mode 100644 (file)
index bbcde90..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2016-2017,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/>. -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_height="wrap_content"
-    android:layout_width="match_parent"
-    android:padding="6dp">
-
-
-    <!-- The `TextInputLayout` makes the `android:hint` float above the `EditText`. -->
-    <com.google.android.material.textfield.TextInputLayout
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent" >
-
-        <!-- `android:imeOptions="actionsGo"` sets the keyboard to have a `go` key instead of a `new line` key.  `android:inputType="textUri"` disables spell check in the `EditText`. -->
-        <com.google.android.material.textfield.TextInputEditText
-            android:id="@+id/download_image_name"
-            android:layout_height="wrap_content"
-            android:layout_width="match_parent"
-            android:hint="@string/image_name"
-            android:imeOptions="actionGo"
-            android:inputType="textUri" />
-    </com.google.android.material.textfield.TextInputLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/save_dialog.xml b/app/src/main/res/layout/save_dialog.xml
new file mode 100644 (file)
index 0000000..5e1765e
--- /dev/null
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2019-2020 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/>. -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent" >
+
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="vertical"
+        android:layout_marginTop="10dp"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp" >
+
+        <!-- The text input layout makes the `android:hint` float above the edit text. -->
+        <com.google.android.material.textfield.TextInputLayout
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent">
+
+            <!-- `android:inputType="TextUri"` disables spell check and places an `/` on the main keyboard. -->
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/url_edittext"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:hint="@string/url"
+                android:inputType="textMultiLine|textUri" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <!-- File size. -->
+        <TextView
+            android:id="@+id/file_size_textview"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginEnd="3dp"
+            android:layout_gravity="end" />
+
+        <!-- Align the edit text and the select file button horizontally. -->
+        <LinearLayout
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="horizontal"
+            android:layout_marginTop="5dp">
+
+            <!-- The text input layout makes the `android:hint` float above the edit text. -->
+            <com.google.android.material.textfield.TextInputLayout
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1" >
+
+                <!-- `android:inputType="textUri"` disables spell check and places an `/` on the main keyboard. -->
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/file_name_edittext"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:hint="@string/file_name"
+                    android:inputType="textMultiLine|textUri" />
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <Button
+                android:id="@+id/browse_button"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/browse" />
+        </LinearLayout>
+
+        <!-- File already exists warning. -->
+        <TextView
+            android:id="@+id/file_exists_warning_textview"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_margin="5dp"
+            android:text="@string/file_exists_warning"
+            android:textColor="?attr/redText"
+            android:textAlignment="center" />
+
+        <!-- Storage permission explanation. -->
+        <TextView
+            android:id="@+id/storage_permission_textview"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/storage_permission_explanation"
+            android:textColor="?android:textColorPrimary"
+            android:textAlignment="center" />
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/save_webpage_dialog.xml b/app/src/main/res/layout/save_webpage_dialog.xml
deleted file mode 100644 (file)
index cbb4f48..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2019-2020 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/>. -->
-
-<ScrollView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="wrap_content"
-    android:layout_width="match_parent" >
-
-    <LinearLayout
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:orientation="vertical"
-        android:layout_marginTop="10dp"
-        android:layout_marginStart="10dp"
-        android:layout_marginEnd="10dp" >
-
-        <!-- The text input layout makes the `android:hint` float above the edit text. -->
-        <com.google.android.material.textfield.TextInputLayout
-            android:layout_height="wrap_content"
-            android:layout_width="match_parent">
-
-            <!-- `android:inputType="TextUri"` disables spell check and places an `/` on the main keyboard. -->
-            <com.google.android.material.textfield.TextInputEditText
-                android:id="@+id/url_edittext"
-                android:layout_height="wrap_content"
-                android:layout_width="match_parent"
-                android:hint="@string/url"
-                android:inputType="textMultiLine|textUri" />
-        </com.google.android.material.textfield.TextInputLayout>
-
-        <!-- Align the edit text and the select file button horizontally. -->
-        <LinearLayout
-            android:layout_height="wrap_content"
-            android:layout_width="match_parent"
-            android:orientation="horizontal"
-            android:layout_marginTop="5dp">
-
-            <!-- The text input layout makes the `android:hint` float above the edit text. -->
-            <com.google.android.material.textfield.TextInputLayout
-                android:layout_height="wrap_content"
-                android:layout_width="0dp"
-                android:layout_weight="1" >
-
-                <!-- `android:inputType="textUri"` disables spell check and places an `/` on the main keyboard. -->
-                <com.google.android.material.textfield.TextInputEditText
-                    android:id="@+id/file_name_edittext"
-                    android:layout_height="wrap_content"
-                    android:layout_width="match_parent"
-                    android:hint="@string/file_name"
-                    android:inputType="textMultiLine|textUri" />
-            </com.google.android.material.textfield.TextInputLayout>
-
-            <Button
-                android:id="@+id/browse_button"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:text="@string/browse" />
-        </LinearLayout>
-
-        <!-- File already exists warning. -->
-        <TextView
-            android:id="@+id/file_exists_warning_textview"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_gravity="center_horizontal"
-            android:layout_margin="5dp"
-            android:text="@string/file_exists_warning"
-            android:textColor="?attr/redText"
-            android:textAlignment="center" />
-
-        <!-- Storage permission explanation. -->
-        <TextView
-            android:id="@+id/storage_permission_textview"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_gravity="center_horizontal"
-            android:text="@string/storage_permission_explanation"
-            android:textColor="?android:textColorPrimary"
-            android:textAlignment="center" />
-    </LinearLayout>
-</ScrollView>
\ No newline at end of file
index e2d1a60902489d4659bce0365a486bee01f3052d..8fd7c3796f097441ce68a631ac0c9085c6b9024f 100644 (file)
@@ -25,7 +25,7 @@
     <!-- `android:iconTint` can be used once the minimum API >= 26 instead of including a separate drawable for each theme. -->
     <item
         android:id="@+id/copy"
-        android:title="@string/copy"
+        android:title="@string/copy_string"
         android:orderInCategory="10"
         android:icon="?attr/copyIcon"
         app:showAsAction="always" />
index 3093e1ec5b25a77c47cd84a7b225edb85959b2ee..75d4a91dc2597b9ec524d2200d266325d2506fe2 100644 (file)
@@ -24,7 +24,7 @@
     <item
         android:id="@+id/clear_and_exit"
         android:title="@string/clear_and_exit"
-        android:icon="@drawable/open_with_external_app_enabled_light"
+        android:icon="@drawable/clear_and_exit"
         android:orderInCategory="10" />
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
@@ -75,7 +75,7 @@
         <item
             android:id="@+id/downloads"
             android:title="@string/downloads"
-            android:icon="@drawable/downloads_light"
+            android:icon="@drawable/downloads"
             android:orderInCategory="80" />
     </group>
 
index 1ff0bb1f2162428bce77e0915d007b3fbba3adfb..78e1cad4eea945f634ff395ba57eabf45a67b306 100644 (file)
@@ -48,9 +48,7 @@
     <string name="form_data_deleted">Formulardaten gelöscht</string>
     <string name="open_navigation_drawer">Navigationspanel öffnen</string>
     <string name="close_navigation_drawer">Navigationspanel schließen</string>
-    <string name="no_title">Kein Titel</string>
     <string name="unrecognized_url">Unbekannte URL:</string>
-    <string name="open_with">Öffnen mit</string>
     <string name="add_tab">Tab hinzufügen</string>
     <string name="close_tab">Tab schließen</string>
     <string name="new_tab">Neuer Tab</string>
     <string name="loading_ultralist">Lade UltraList</string>
     <string name="loading_ultraprivacy">Lade UltraPrivacy</string>
 
-    <!-- Save As. -->
-    <string name="save_as">Speichern Unter</string>
-    <string name="save_image_as">Bild Speichern Unter</string>
-    <string name="file_name">Dateiname</string>
-    <string name="image_name">Name des Bildes</string>
-    <string name="unknown_size">Unbekannte Größe</string>
-    <string name="download">Download</string>
-    <string name="cannot_download_file">Diese Datei kann nicht heruntergeladen werden, da sie keine HTTP oder HTTPS URI besitzt.</string>
-    <string name="cannot_download_image">Dieses Bild kann nicht heruntergeladen werden, da es keine HTTP oder HTTPS URI besitzt.</string>
-
     <!-- Custom App Bar. -->
     <string name="favorite_icon">Website-Icon</string>
     <string name="url_or_search_terms">URL oder Suchbegriff</string>
     <string name="open_in_background">Im Hintergrund öffnen</string>
     <string name="open_image_in_new_tab">Grafik in neuem Tab öffnen</string>
     <string name="copy_url">URL kopieren</string>
-    <string name="download_url">Download URL</string>
     <string name="email_address">E-Mail-Adresse</string>
     <string name="copy_email_address">E-Mail-Adresse kopieren</string>
     <string name="write_email">E-Mail senden</string>
     <string name="view_image">Bild anzeigen</string>
-    <string name="download_image">Bild herunterladen</string>
 
     <!-- Find on Page. -->
     <string name="previous">Vorheriges</string>
     <string name="next">Nächstes</string>
 
     <!-- Save Webpage. -->
+    <string name="file_name">Dateiname</string>
     <string name="save_archive">Archiv speichern</string>
     <string name="save_image">Grafik speichern</string>
     <string name="webpage_mht">Webseite.mht</string>
     <string name="webpage_png">Webseite.png</string>
+    <string name="unknown_size">Unbekannte Größe</string>
+    <string name="ok">OK</string>
     <string name="saving_image">Speichere Grafik…</string>
     <string name="image_saved">Grafik gespeichert.</string>
     <string name="error_saving_image">Fehler beim Speichern der Grafik:</string>
     <string name="response_message">Status-Code</string>
     <string name="response_headers">Antwortkopfzeilen</string>
     <string name="response_body">Antwortinhalt</string>
-    <string name="error_body">Fehler</string>
     <string name="about_view_source">Über Quelltext</string>
     <string name="about_view_source_message">Weil Androids WebView keine Quelltext-Informationen zur Verfügung stellt, muss eine separate a separate Serveranfrage mit system tools gestellt werden,
         die hier dargestellten Daten erhält. Deshalb können Unterschiede zwischen diesen Daten und der mit WebView dargestellten Webseite auftreten.
     <string name="proxies">Proxies</string>
     <string name="tracking_ids">Verolgungs-IDs</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Download-Zielordner</string>
-    <string name="download_location_message">Privacy Browser benötigt die Berechtigung zur Speicherung im öffentlichen Download-Ordner. Anderenfalls wird der Download-Ordner der App verwendet.</string>
-    <string name="ok">OK</string>
-
     <!-- Proxy. -->
     <string name="orbot_not_installed_title">Orbot ist nicht installiert</string>
     <string name="orbot_not_installed_message">Der Orbot-Proxy kann erst nach Installation der Orbot-App genutzt werden.</string>
         <string name="display_additional_app_bar_icons">Weitere Icons in der Titelleiste</string>
         <string name="display_additional_app_bar_icons_summary">Zeigt in der App-Leiste die Icons zum Neu-Laden der Webseite und - sofern Platz vorhanden ist -
             zum Umschalten von Cookies und DOM Storage.</string>
-        <string name="download_with_external_app">Mit externer App herunterladen</string>
-        <string name="download_with_external_app_summary">Android’s Download-Manager funktioniert auf manchen Geräten nicht zufriedenstellend.</string>
         <string name="dark_theme">Dunkles Thema</string>
         <string name="dark_theme_summary">Wechseln des Themas startet Privacy Browser neu.</string>
         <string name="night_mode">Nacht-Modus</string>
index 348c82704686ba4f32e1233d451f2c29f121a03f..1d7bd26586c37a5b45f30b3f0e7fcd2095dba99a 100644 (file)
@@ -44,9 +44,7 @@
     <string name="form_data_deleted">Datos de formulario borrado</string>
     <string name="open_navigation_drawer">Abrir la caja de navegación</string>
     <string name="close_navigation_drawer">Cerrar la caja de navegación</string>
-    <string name="no_title">Sin título</string>
     <string name="unrecognized_url">URL no reconocida:</string>
-    <string name="open_with">Abrir con</string>
     <string name="add_tab">Añadir pestaña</string>
     <string name="close_tab">Cerrar pestaña</string>
     <string name="new_tab">Nueva pestaña</string>
     <string name="loading_ultralist">Cargando UltraList</string>
     <string name="loading_ultraprivacy">Cargando Ultra Privacidad</string>
 
-    <!-- Save As. -->
-    <string name="save_as">Guardar como</string>
-    <string name="save_image_as">Guardar imagen como</string>
-    <string name="file_name">Nombre de archivo</string>
-    <string name="image_name">Nombre de imagen</string>
-    <string name="unknown_size">Tamaño desconocido</string>
-    <string name="download">Descargar</string>
-    <string name="cannot_download_file">Este archivo no puede descargarse porque no tiene un URI HTTP o HTTPS.</string>
-    <string name="cannot_download_image">Esta imagen no puede descargarse porque no tiene un URI HTTP o HTTPS.</string>
-
     <!-- Custom App Bar. -->
     <string name="favorite_icon">Icono favorito</string>
     <string name="url_or_search_terms">URL o búsqueda</string>
     <string name="open_in_background">Abrir en segundo plano</string>
     <string name="open_image_in_new_tab">Abrir imagen en nueva pestaña</string>
     <string name="copy_url">Copiar URL</string>
-    <string name="download_url">Descargar URL</string>
     <string name="email_address">Correo electrónico</string>
     <string name="copy_email_address">Copiar correo electrónico</string>
     <string name="write_email">Escribir correo electrónico</string>
     <string name="view_image">Ver imagen</string>
-    <string name="download_image">Descargar imagen</string>
 
     <!-- Find on Page. -->
     <string name="previous">Anterior</string>
     <string name="next">Siguiente</string>
 
     <!-- Save Webpage. -->
+    <string name="file_name">Nombre de archivo</string>
     <string name="save_archive">Guardar archivo</string>
     <string name="save_image">Guardar imagen</string>
     <string name="webpage_mht">PaginaWeb.mht</string>
     <string name="webpage_png">PaginaWeb.png</string>
+    <string name="unknown_size">Tamaño desconocido</string>
+    <string name="ok">OK</string>
     <string name="saving_image">Guardando imagen…</string>
     <string name="image_saved">Imagen guardada.</string>
     <string name="error_saving_image">Error guardando imagen:</string>
     <string name="response_message">Mensaje de respuesta</string>
     <string name="response_headers">Cabeceras de respuesta</string>
     <string name="response_body">Cuerpo de respuesta</string>
-    <string name="error_body">Cuerpo del error</string>
     <string name="about_view_source">Acerca de ver la fuente</string>
     <string name="about_view_source_message">Debido a que WebView de Android no expone la información fuente,
         se hizo una solicitud por separado utilizando las herramientas del sistema para recopilar la información mostrada en esta actividad.
     <string name="proxies">Proxis</string>
     <string name="tracking_ids">Rastreo de IDs</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Lugar de descarga</string>
-    <string name="download_location_message">Navegador Privado necesita el permiso de almacenamiento para utilizar el directorio de descarga público.
-        Si se deniega, se utilizará el directorio de descarga de la aplicación.</string>
-    <string name="ok">OK</string>
-
     <!-- Proxy. -->
     <string name="orbot_not_installed_title">Orbot No Instalado</string>
     <string name="orbot_not_installed_message">El proxy a través de Orbot no funcionará a menos que la aplicación Orbot esté instalada.</string>
         <string name="scroll_app_bar_summary">Desplazar la barra de aplicaciones desde la parte superior de la pantalla cuando el WebView se desplaza hacia abajo.</string>
         <string name="display_additional_app_bar_icons">Mostrar iconos adicionales en la barra de aplicación</string>
         <string name="display_additional_app_bar_icons_summary">Mostrar iconos en la barra de aplicaciones para refrescar el WebView y, si hay espacio, para alternar entre cookies y almacenamiento DOM.</string>
-        <string name="download_with_external_app">Descargar con app externa</string>
-        <string name="download_with_external_app_summary">El gestor de descargas de Android no funciona bien en algunos dispositivos.</string>
         <string name="dark_theme">Tema oscuro</string>
         <string name="dark_theme_summary">Cambiar el tema reiniciará Navegador Privado.</string>
         <string name="night_mode">Modo noche</string>
index 9fa8a2d37b0f4218906538106aa39df64a3323c9..4a227f107e1bc8e8b794a19efe505c0bf2e33cc9 100644 (file)
@@ -45,9 +45,7 @@
     <string name="form_data_deleted">Données de formulaires supprimées</string>
     <string name="open_navigation_drawer">Ouvrir le panneau de navigation</string>
     <string name="close_navigation_drawer">Fermer le panneau de navigation</string>
-    <string name="no_title">Sans titre</string>
     <string name="unrecognized_url">URL inconnue:</string>
-    <string name="open_with">Ouvrir avec</string>
     <string name="add_tab">Ajouter un onglet</string>
     <string name="close_tab">Fermer l\'onglet</string>
     <string name="new_tab">Nouvel onglet</string>
     <string name="loading_ultralist">Chargement UltraList</string>
     <string name="loading_ultraprivacy">Chargement UltraPrivacy</string>
 
-    <!-- Save As. -->
-    <string name="save_as">Sauvergarder sous</string>
-    <string name="save_image_as">Sauvergarder l\'image sous</string>
-    <string name="file_name">Nom du fichier</string>
-    <string name="image_name">Nom de l\'image</string>
-    <string name="unknown_size">taille inconnue</string>
-    <string name="download">Télécharger</string>
-    <string name="cannot_download_file">Ce fichier ne peut être téléchargé car il n\'a pas d\'URI du type HTTP ou HTTPS.</string>
-    <string name="cannot_download_image">Cette image ne peut être téléchargée car elle n\'a pas d\'URI du type HTTP ou HTTPS.</string>
-
     <!-- Custom App Bar. -->
     <string name="favorite_icon">Icône de Favoris</string>
     <string name="url_or_search_terms">Rechercher ou saisir adresse</string>
     <string name="open_in_background">Ouvrir en arrière-plan</string>
     <string name="open_image_in_new_tab">Ouvrir l\'image dans un nouvel onglet</string>
     <string name="copy_url">Copier l\'URL</string>
-    <string name="download_url">Télécharger la cible</string>
     <string name="email_address">Addresse e-mail</string>
     <string name="copy_email_address">Copier l\'adresse e-mail</string>
     <string name="write_email">Ecrire un e-mail</string>
     <string name="view_image">Voir l\'image</string>
-    <string name="download_image">Télécharger l\'image</string>
 
     <!-- Find on Page. -->
     <string name="previous">Précédent</string>
     <string name="next">Suivant</string>
 
     <!-- Save Webpage. -->
+    <string name="file_name">Nom du fichier</string>
     <string name="save_archive">Enregistrer l\'archive</string>
     <string name="save_image">Sauvegarder en tant qu\'image</string>
     <string name="webpage_mht">PageWeb.mht</string>
     <string name="webpage_png">PageWeb.png</string>
+    <string name="unknown_size">taille inconnue</string>
+    <string name="ok">OK</string>
     <string name="saving_image">Sauvegarde en cours…</string>
     <string name="image_saved">Image sauvegardée.</string>
     <string name="error_saving_image">Erreur durant la sauvegarde :</string>
     <string name="response_message">Message de la réponse</string>
     <string name="response_headers">En-tête de la réponse</string>
     <string name="response_body">Corps de la réponse</string>
-    <string name="error_body">Erreur dans le corps de réponse</string>
     <string name="about_view_source">A propos View Source</string>
     <string name="about_view_source_message">Puisqu\'Android Webview ne permet pas de révêler l\'information source,
         une requête séparée a été effectuée en utilisant les outils sytèmes afin d\'afficher ce qui est présenté à l\'écran
     <string name="proxies">Proxies</string>
     <string name="tracking_ids">ID de suivi</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Dossier de téléchargement</string>
-    <string name="download_location_message">Privacy Browser nécessite les droits d\'accès au stockage pour accéder aux dossiers publics.
-        Si cela est refusé, les dossiers internes à l\'application peut néanmoins être utilisé.</string>
-    <string name="ok">OK</string>
-
     <!-- Proxy. -->
     <string name="orbot_not_installed_title">Orbot non installé</string>
     <string name="orbot_not_installed_message">Le proxy via Orbot ne fonctionnera que si l\'application Orbot est installée.</string>
         <string name="display_additional_app_bar_icons">Icônes supplémentaires dans la barre d\'applications</string>
         <string name="display_additional_app_bar_icons_summary">Affichez des icônes dans la barre d\'applications pour actualiser WebView et, le cas échéant,
             pour activer/désactiver les cookies et le stockage DOM.</string>
-        <string name="download_with_external_app">Téléchargement externe</string>
-        <string name="download_with_external_app_summary">Le gestionnaire de téléchargement Android ne fonctionne pas bien sur certains appareils.</string>
         <string name="dark_theme">Thème sombre</string>
         <string name="dark_theme_summary">Le changement de thème redémarre Privacy Browser.</string>
         <string name="night_mode">Mode nuit</string>
index eb454c1d82cc9f25823fb75417527a6c2f038aa4..1170c8d23a3e61655a7f09635984f92851068a78 100644 (file)
@@ -44,9 +44,7 @@
     <string name="form_data_deleted">Dati dei Moduli eliminati</string>
     <string name="open_navigation_drawer">Apri il menu di navigazione</string>
     <string name="close_navigation_drawer">Chiudi il menu di navigazione</string>
-    <string name="no_title">Nessun titolo</string>
     <string name="unrecognized_url">URL non riconosciuta:</string>
-    <string name="open_with">Apri con</string>
     <string name="add_tab">Aggiungi Scheda</string>
     <string name="close_tab">Chiudi Scheda</string>
     <string name="new_tab">Nuova Scheda</string>
     <string name="loading_ultralist">Caricamento UltraList</string>
     <string name="loading_ultraprivacy">Caricamento UltraPrivacy</string>
 
-    <!-- Save As. -->
-    <string name="save_as">Salva come</string>
-    <string name="save_image_as">Salva l\'immagine come</string>
-    <string name="file_name">Nome File</string>
-    <string name="image_name">Nome Immagine</string>
-    <string name="unknown_size">Dimensione sconosciuta</string>
-    <string name="download">Scarica</string>
-    <string name="cannot_download_file">Il file non può essere scaricato perché non ha un URL HTTP o HTTPS</string>
-    <string name="cannot_download_image">L\'immagine non può essere scaricata perché non ha un URL HTTP o HTTPS</string>
-
     <!-- Custom App Bar. -->
     <string name="favorite_icon">FavIcon</string>
     <string name="url_or_search_terms">Digita URL o Ricerca</string>
     <string name="open_in_background">Apri in Background</string>
     <string name="open_image_in_new_tab">Apri l\'immagine in una nuova scheda</string>
     <string name="copy_url">Copia URL</string>
-    <string name="download_url">Scarica URL</string>
     <string name="email_address">Indirizzo Email</string>
     <string name="copy_email_address">Copia Indirizzo Email</string>
     <string name="write_email">Scrivi Email</string>
     <string name="view_image">Apri Immagine</string>
-    <string name="download_image">Scarica Immagine</string>
 
     <!-- Find on Page. -->
     <string name="previous">Precedente</string>
     <string name="next">Successivo</string>
 
     <!-- Save Webpage. -->
+    <string name="file_name">Nome File</string>
     <string name="save_archive">Salva Archivio</string>
     <string name="save_image">Salva Immagine</string>
     <string name="webpage_mht">PaginaWeb.mht</string>
     <string name="webpage_png">PaginaWeb.png</string>
+    <string name="unknown_size">Dimensione sconosciuta</string>
+    <string name="ok">OK</string>
     <string name="saving_image">Salvataggio immagine…</string>
     <string name="image_saved">Immagine salvata.</string>
     <string name="error_saving_image">Errore nel salvare l\'immagine:</string>
     <string name="response_message">Messaggio di Risposta</string>
     <string name="response_headers">Risposta Intestazioni</string>
     <string name="response_body">Testo della Risposta</string>
-    <string name="error_body">Testo Errore</string>
     <string name="about_view_source">Informazioni sulla visualizzazione della sorgente</string>
     <string name="about_view_source_message">Dal momento che la WebView di Android non fornisce indicazioni sulla sorgente è stata effettuata una richiesta separata utilizzando i system tools in modo da
         ottenere le informazioni mostrate. Potrebbero esserci alcune differenze tra questi dati e quelli utilizzati da WebView.
     <string name="proxies">Proxy</string>
     <string name="tracking_ids">Tracciamento utenti</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Cartella di Download</string>
-    <string name="download_location_message">Privacy Browser necessita del permesso di accesso alla memoria di storage per utilizzare la cartella pubblica di download.
-        Nel caso in cui il permesso sia negato sarà utilizzata la cartella di download dell\'applicazione.</string>
-    <string name="ok">OK</string>
-
     <!-- Proxy. -->
     <string name="orbot_not_installed_title">Orbot Non Installato</string>
     <string name="orbot_not_installed_message">Il Proxy con Orbot non funziona se non è installata la app Orbot.</string>
         <string name="display_additional_app_bar_icons">Mostra icone addizionali nella barra dell\'applicazione</string>
         <string name="display_additional_app_bar_icons_summary">Mostra nella barra dell\'applicazione le icone per l\'aggiornamento di WebView e, se lo spazio è sufficiente,
             per l\'attivazione dei cookie e del DOM storage.</string>
-        <string name="download_with_external_app">Scarica con un\'applicazione esterna</string>
-        <string name="download_with_external_app_summary">Il download manager di Android potrebbe non funzionare correttamente su alcuni dispositivi.</string>
         <string name="dark_theme">Tema Dark</string>
         <string name="dark_theme_summary">La modifica del tema provocherà il riavvio di Privacy Browser.</string>
         <string name="night_mode">Modalità Notte</string>
index b85e0ddc5a646c17facd5d289a32ef839bbbb0c7..3e45aa4c9278e3503f8e4501fd8e3d8193c02231 100644 (file)
@@ -42,9 +42,7 @@
     <string name="form_data_deleted">Данные формы удалены</string>
     <string name="open_navigation_drawer">Открыть панель навигации</string>
     <string name="close_navigation_drawer">Закрыть панель навигации</string>
-    <string name="no_title">Без названия</string>
     <string name="unrecognized_url">Нераспознанный URL:</string>
-    <string name="open_with">Открыть в</string>
     <string name="add_tab">Добавить вкладку</string>
     <string name="close_tab">Закрыть вкладку</string>
     <string name="new_tab">Новая вкладка</string>
     <string name="loading_ultralist">Загрузка UltraList</string>
     <string name="loading_ultraprivacy">Загрузка UltraPrivacy</string>
 
-    <!-- Save As. -->
-    <string name="save_as">Сохранить как</string>
-    <string name="save_image_as">Сохранить изображение как</string>
-    <string name="file_name">Имя файла</string>
-    <string name="image_name">Название изображения</string>
-    <string name="unknown_size">неизвестный размер</string>
-    <string name="download">Скачать</string>
-    <string name="cannot_download_file">Невозможно загрузить этот файл, поскольку он не содержит идентификатор ресурса HTTP или HTTPS.</string>
-    <string name="cannot_download_image">Невозможно загрузить это изображение, поскольку оно не содержит идентификатор ресурса HTTP или HTTPS.</string>
-
     <!-- Custom App Bar. -->
     <string name="favorite_icon">Значок сайта</string>
     <string name="url_or_search_terms">URL или поисковый запрос</string>
     <string name="open_in_background">Открыть в фоне</string>
     <string name="open_image_in_new_tab">Открыть изображение в новой вкладке</string>
     <string name="copy_url">Копировать URL</string>
-    <string name="download_url">Загрузить URL</string>
     <string name="email_address">Адрес email</string>
     <string name="copy_email_address">Копировать адрес email</string>
     <string name="write_email">Написать email</string>
     <string name="view_image">Просмотр изображения</string>
-    <string name="download_image">Скачать изображение</string>
 
     <!-- Find on Page. -->
     <string name="previous">Предыдущий</string>
     <string name="next">Следующий</string>
 
     <!-- Save Webpage. -->
+    <string name="file_name">Имя файла</string>
     <string name="save_archive">Сохранить архив</string>
     <string name="save_image">Сохранить изображение</string>
     <string name="webpage_mht">Webpage.mht</string>
     <string name="webpage_png">Webpage.png</string>
+    <string name="unknown_size">неизвестный размер</string>
+    <string name="ok">OK</string>
     <string name="saving_image">Сохранение изображения…</string>
     <string name="image_saved">Изображение сохранено.</string>
     <string name="error_saving_image">Ошибка сохранения изображения:</string>
     <string name="response_message">Ответное сообщение</string>
     <string name="response_headers">Заголовки ответа</string>
     <string name="response_body">Тело ответа</string>
-    <string name="error_body">Тело ошибки</string>
     <string name="about_view_source">О просмотре исходного кода</string>
     <string name="about_view_source_message">Поскольку Android WebView не предоставляет исходные данные, для сбора информации, отображаемой в этом действии,
         был сделан отдельный запрос с помощью системных средств. Между этими данными и теми, которые используются в WebView, могут быть некоторые отличия.
     <string name="proxies">Прокси</string>
     <string name="tracking_ids">Идентификаторы отслеживания</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Папка загрузок</string>
-    <string name="download_location_message">Privacy Browser необходимо разрешение на доступ к хранилищу для использования общей папки загрузок.
-        Если разрешение получено не будет, то для загрузок будет использоваться папка приложения</string>
-    <string name="ok">OK</string>
-
     <!-- Proxy. -->
     <string name="orbot_not_installed_title">Orbot не установлен</string>
     <string name="orbot_not_installed_message">Прокси через Orbot работать не будет, если приложение Orbot не установлено.</string>
         <string name="scroll_app_bar_summary">Прокручивает панель приложения вверху экрана при прокрутке WebView вниз.</string>
         <string name="display_additional_app_bar_icons">Отображать дополнительные значки на панели приложения</string>
         <string name="display_additional_app_bar_icons_summary">Отображать значки на панели приложения для обновления WebView и, при наличии места, для переключения файлов cookie и хранилища DOM</string>
-        <string name="download_with_external_app">Загрузка с помощью внешнего приложения</string>
-        <string name="download_with_external_app_summary">Менеджер загрузок Android не работает на некоторых устройствах.</string>
         <string name="dark_theme">Темная тема</string>
         <string name="dark_theme_summary">Изменение темы перезапускает Privacy Browser.</string>
         <string name="night_mode">Ночной режим</string>
index 0c8a28245ffde9895f35759923534647f5f22edc..c1ac660a0664cc866966d590d54c0dd762e5715b 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2015-2020 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -42,9 +42,7 @@
     <string name="form_data_deleted">Form Verisi silindi</string>
     <string name="open_navigation_drawer">Gezinti Menüsünü aç</string>
     <string name="close_navigation_drawer">Gezinti Menüsünü kapa</string>
-    <string name="no_title">Başlıksız</string>
     <string name="unrecognized_url">Onaylanmamış URL:</string>
-    <string name="open_with">Bununla aç</string>
     <string name="add_tab">Sekme ekle</string>
     <string name="close_tab">Sekmeyi kapa</string>
     <string name="new_tab">Yeni sekme</string>
     <string name="loading_ultralist">UltraList yükleniyor</string>
     <string name="loading_ultraprivacy">UltraPrivacy yükleniyor</string>
 
-    <!-- Save As. -->
-    <string name="save_as">Farklı kaydet</string>
-    <string name="save_image_as">Resmi farklı kaydet</string>
-    <string name="file_name">Dosya adı</string>
-    <string name="image_name">Resim adı</string>
-    <string name="unknown_size">Bilinmeyen boyut</string>
-    <string name="download">İndir</string>
-    <string name="cannot_download_file">HTTP veya HTTPS kaynak tanımlayıcısı içermediği için bu dosya indirilemiyor.</string>
-    <string name="cannot_download_image">HTTP veya HTTPS kaynak tanımlayıcısı içermediği için bu resim indirilemiyor.</string>
-
     <!-- Custom App Bar. -->
     <string name="favorite_icon">Site Simgesi</string>
     <string name="url_or_search_terms">URL veya Arama Terimleri</string>
     <string name="open_in_new_tab">Yeni sekmede aç</string>
     <string name="open_image_in_new_tab">Resmi Yeni Sekmede Aç</string>
     <string name="copy_url">URL\'yi kopyala</string>
-    <string name="download_url">URL\'yi indir</string>
     <string name="email_address">E-posta adresi</string>
     <string name="copy_email_address">E-posta adresini kopyala</string>
     <string name="write_email">E-posta yaz</string>
     <string name="view_image">Resmi görüntüle</string>
-    <string name="download_image">Resmi indir</string>
 
     <!-- Find on Page. -->
     <string name="previous">Önceki</string>
     <string name="next">Sonraki</string>
 
     <!-- Save Webpage. -->
+    <string name="file_name">Dosya adı</string>
     <string name="save_image">Resmi kaydet</string>
     <string name="webpage_png">Websayfası.png</string>
+    <string name="unknown_size">Bilinmeyen boyut</string>
+    <string name="ok">OK</string>
     <string name="saving_image">Resim kaydediliyor…</string>
     <string name="image_saved">Resim kaydedildi</string>
     <string name="error_saving_image">Resim kaydı başarısız:</string>
     <string name="response_message">Yanıt Mesajı</string>
     <string name="response_headers">Yanıt Başlıkları</string>
     <string name="response_body">Yanıt Metni</string>
-    <string name="error_body">Hata Metni</string>
     <string name="about_view_source">Kaynağı Görüntüle Hakkında</string>
     <string name="about_view_source_message">Android WebView kaynak bilgisini gösteremediğinden, bu etkinlikte gösterilen bilgiyi toplamak için sistem araçları kullanılarak ayrı bir istek yapıldı.
         Elde edilen veri ile ana etkinlikteki WebView\'ın kullandığı veri arasında farklılıklar olabilir. Bu sorun, 4.x serisinde Privacy WebView sürümüyle ortadan kalkacaktır.</string>
     <string name="ssl_certificates">SSL Sertifikaları</string>
     <string name="tracking_ids">İzleme Kimlikleri</string>
 
-    <!-- Download Location -->
-    <string name="download_location">İndirme Konumu</string>
-    <string name="download_location_message">Privacy Browser\'ın genel indirme dizinini kullanması için depolama alanı izni gerekmektedir.
-        Eğer reddedilirse, onun yerine uygulamanın indirme dizini kullanılacaktır.</string>
-    <string name="ok">OK</string>
-
     <!-- Orbot. -->
     <string name="waiting_for_orbot">Orbot\'un bağlanması bekleniyor.</string>
 
         <string name="scroll_app_bar_summary">WebView aşağı kaydırıldığında, ekranın üst kısmından uygulama çubuğunu kaydırır.</string>
         <string name="display_additional_app_bar_icons">Ek uygulama çubuğu simgelerini göster</string>
         <string name="display_additional_app_bar_icons_summary">WebView\'ı yenilemek, yer varsa çerezleri ve DOM depolamayı değiştirmek için simgeleri uygulama çubuğunda gösterir.</string>
-        <string name="download_with_external_app">Harici uygulamayla indir</string>
-        <string name="download_with_external_app_summary">Android indirme yönetecisi bazı cihazlarda iyi çalışmayabilir.</string>
         <string name="dark_theme">Koyu tema</string>
         <string name="dark_theme_summary">Temayı değiştirmek Privacy Browser\'ı yeniden başlatır.</string>
         <string name="night_mode">Gece modu</string>
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
deleted file mode 100644 (file)
index 55ebf55..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<resources>
-    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
-         (such as screen margins) for screens with more than 820dp of available width. This
-         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
-    <dimen name="activity_horizontal_margin">64dp</dimen>
-</resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
deleted file mode 100644 (file)
index 2512a38..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<resources>
-    <!-- Default screen margins, per the Android Design guidelines. -->
-    <dimen name="activity_horizontal_margin">16dp</dimen>
-    <dimen name="activity_vertical_margin">16dp</dimen>
-</resources>
index fd1fe5d518e595d2ce54954ecd0a12a7b3e4b597..cd9045dc5f22dead161dd06e6c5f3d4b88758a6f 100644 (file)
@@ -49,9 +49,7 @@
     <string name="form_data_deleted">Form Data deleted</string>
     <string name="open_navigation_drawer">Open Navigation Drawer</string>
     <string name="close_navigation_drawer">Close Navigation Drawer</string>
-    <string name="no_title">No title</string>
     <string name="unrecognized_url">Unrecognized URL:</string>
-    <string name="open_with">Open with</string>
     <string name="add_tab">Add tab</string>
     <string name="close_tab">Close Tab</string>
     <string name="new_tab">New tab</string>
     <string name="loading_ultraprivacy">Loading UltraPrivacy</string>
 
     <!-- Save As. -->
-    <string name="save_as">Save As</string>
-    <string name="save_image_as">Save Image As</string>
-    <string name="file_name">File name</string>
-    <string name="image_name">Image name</string>
-    <string name="unknown_size">unknown size</string>
-    <string name="download">Download</string>
-    <string name="cannot_download_file">This file cannot be downloaded because it does not have an HTTP or HTTPS URI.</string>
-    <string name="cannot_download_image">This image cannot be downloaded because it does not have an HTTP or HTTPS URI.</string>
 
     <!-- Custom App Bar. -->
     <string name="favorite_icon">Favorite Icon</string>
     <string name="open_in_background">Open in Background</string>
     <string name="open_image_in_new_tab">Open Image in New Tab</string>
     <string name="copy_url">Copy URL</string>
-    <string name="download_url">Download URL</string>
     <string name="email_address">Email Address</string>
     <string name="copy_email_address">Copy Email Address</string>
     <string name="write_email">Write Email</string>
     <string name="view_image">View Image</string>
-    <string name="download_image">Download Image</string>
 
     <!-- Find on Page. -->
     <string name="zero_of_zero" translatable="false">0/0</string>
 
     <!-- Save Webpage. -->
     <string name="save_dialog" translatable="false">Save Dialog</string>  <!-- This string is used to tag the save dialog.  It is never displayed to the user. -->
+    <string name="file_name">File name</string>
     <string name="save_archive">Save Archive</string>
     <string name="save_image">Save Image</string>
     <string name="webpage_mht">Webpage.mht</string>
     <string name="webpage_png">Webpage.png</string>
     <string name="file">File</string>
-    <string name="saving_file">Saving file…</string>
+    <string name="bytes">bytes</string>
+    <string name="unknown_size">unknown size</string>
+    <string name="bad_url">bad URL</string>
+    <string name="ok">OK</string>
+    <string name="saving_file">Saving file</string>
     <string name="saving_image">Saving image…</string>
     <string name="file_saved">File saved.</string>
     <string name="image_saved">Image saved.</string>
     <string name="response_message">Response Message</string>
     <string name="response_headers">Response Headers</string>
     <string name="response_body">Response Body</string>
-    <string name="error_body">Error Body</string>
     <string name="about_view_source">About View Source</string>
     <string name="about_view_source_message">Because Android’s WebView does not expose the source information,
         a separate request was made using system tools to gather the information displayed in this activity.
     <string name="proxies">Proxies</string>
     <string name="tracking_ids">Tracking IDs</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Download Location</string>
-    <string name="download_location_message">Privacy Browser needs the storage permission to use the public download directory. If it is denied, the app’s download directory will be used instead.</string>
-    <string name="ok">OK</string>
-
     <!-- Proxy. -->
     <string name="proxy_not_installed_dialog" translatable="false">Proxy not installed dialog</string>  <!-- This string is used to tag the proxy not installed dialog.  It is never displayed to the user. -->
     <string name="waiting_for_proxy_dialog" translatable="false">Waiting for proxy dialog</string>  <!-- This string is used to tag the waiting for proxy dialog.  It is never displayed to the user. -->
         <string name="scroll_app_bar_summary">Scroll the app bar off the top of the screen when the WebView scrolls down.</string>
         <string name="display_additional_app_bar_icons">Display additional app bar icons</string>
         <string name="display_additional_app_bar_icons_summary">Display icons in the app bar for refreshing the WebView and, if there is room, for toggling cookies and DOM storage.</string>
-        <string name="download_with_external_app">Download with external app</string>
-        <string name="download_with_external_app_summary">Android’s download manager doesn’t work well on some devices.</string>
         <string name="dark_theme">Dark theme</string>
         <string name="dark_theme_summary">Changing the theme will restart Privacy Browser.</string>
         <string name="night_mode">Night mode</string>
index 6bbaedf504ac8274b8d74867fab20da16c2d5179..cd849e2b287c577312be2f13991346d3c35908f2 100644 (file)
         <item name="colorAccent">@color/blue_700</item>
     </style>
 
-    <style name="PrivacyBrowserProgressBar" parent="Widget.AppCompat.ProgressBar.Horizontal" >
-        <item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
-    </style>
-
 
     <!-- Dark theme styles. -->
 
index 3bae796fd392832c72b38270b5297bb80bf0b545..dafe62e6d232805e2d9739b6c0bf98823a91346e 100644 (file)
             android:summary="@string/display_additional_app_bar_icons_summary"
             android:defaultValue="false" />
 
-        <SwitchPreference
-            android:key="download_with_external_app"
-            android:title="@string/download_with_external_app"
-            android:summary="@string/download_with_external_app_summary"
-            android:defaultValue="false" />
-
         <SwitchPreference
             android:key="dark_theme"
             android:title="@string/dark_theme"
index 46d1616dad483d7eaa8ebac509fc598fdb1b74ee..54fa952fc7504a2d17799e7750591b669c2be6bc 100644 (file)
@@ -26,7 +26,7 @@ buildscript {
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.3'
+        classpath 'com.android.tools.build:gradle:3.6.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61"
 
         // NOTE: Do not place your application dependencies here; they belong
index 8ba8cce0489f7b38b265b40c3ce9d6161072fc98..19f9da8f1990f8e2f040839bba047d44f955fc11 100644 (file)
@@ -1,6 +1,6 @@
-#Thu Aug 22 13:33:48 MST 2019
+#Thu Mar 05 14:07:17 MST 2020
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip