<a href="https://material.io/icons/">Android Material icon set</a> and are released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.
Modifications copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
- <p><svg class="left"><use href="../shared_images/create_folder.svg#icon"/></svg> is derived from <code>create_new_folder</code>,
- which is part of the <a href="https://material.io/icons/">Android Material icon set</a> and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.
- Modifications copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
- The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
- <p><svg class="left"><use href="../shared_images/clear_and_exit.svg#icon"/></svg> is derived from <code>exit_to_app</code>,
- which is part of the <a href="https://material.io/icons/">Android Material icon set</a>
- and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>. Modifications copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
- The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
- <p><svg class="left"><use href="../shared_images/night_mode.svg#icon"/></svg> is derived from <code>compare</code>,
- which is part of the <a href="https://material.io/icons/">Android Material icon set</a> and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.
- Modifications copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
- The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
- <p><img class="left" src="../shared_images/sort_selected.svg"/> is derived from <code>sort</code>, which is part of the <a href="https://material.io/icons/">Android Material icon set</a>
- and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>. Modifications copyright 2019, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
- The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
- <p><img class="left" src="../shared_images/push_pin_filled_selected.svg"> is derived from <code>push_pin_selected</code>,
- which is part of the <a href="https://material.io/icons/">Android Material icon set</a> and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.
- Modifications copyright 2019-2020, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
- The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
+ <p><svg class="left"><use href="../shared_images/create_folder.svg#icon"/></svg> ist abgeleitet von <code>create_new_folder</code>,
+ das Teil des <a href="https://material.io/icons/">Android Material icon set</a> unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird.
+ Änderungen Copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+ Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
+ <p><svg class="left"><use href="../shared_images/clear_and_exit.svg#icon"/></svg> ist abgeleitet von <code>exit_to_app</code>,
+ das Teil des <a href="https://material.io/icons/">Android Material icon set</a> unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird.
+ Änderungen Copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+ Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
+ <p><svg class="left"><use href="../shared_images/night_mode.svg#icon"/></svg> ist abgeleitet von <code>compare</code>,
+ das Teil des <a href="https://material.io/icons/">Android Material icon set</a> unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird.
+ Änderungen Copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+ Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
+ <p><img class="left" src="../shared_images/sort_selected.svg"/> ist abgeleitet von <code>sort</code>, das Teil des <a href="https://material.io/icons/">Android Material icon set</a>
+ unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird. Änderungen Copyright 2019, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+ Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
+ <p><img class="left" src="../shared_images/push_pin_filled_selected.svg"> ist abgeleitet von <code>push_pin_selected</code>,
+ das Teil des <a href="https://material.io/icons/">Android Material icon set</a> unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird.
+ Änderungen Copyright 2019-2020, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+ Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
<p><svg class="left"><use href="../shared_images/cookie.svg#icon"/></svg> <code>cookie</code> was created by Google.
It is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
and can be downloaded from <a href="https://materialdesignicons.com/icon/cookie">Material Design Icons</a>. It is unchanged except for layout information like color and size.</p>
<a href="https://redmine.stoutner.com/boards/1/topics/243">bis das Menü angezeigt wird</a> und wischen dann, um es zu öffnen.
Auf manchen Geräten mit abgerundeten Kanten kann das Ausklappen des Menüs schwierig sein. Daher kann das Lesezeichen-Menü auch über das Options-Menü geöffnet werden.</p>
- <p>Tapping on a bookmark opens it in the current tab. Long-pressing on a bookmark opens it in a new tab and long-pressing on a folder opens all the bookmarks it directly contains in new tabs.
+ <p>Kurzes Antippen eines Lesezeichens öffnet dieses im aktuellen Tab,
+ langes Drücken öffnet es in einem neuen Tab und langes Drücken auf einen Ordner öffnet alle direkt darin enthaltenen Lesezeichen in neuen Tabs.
Durch Antippen des obersten Symbols im Lesezeichen-Menü können abgespeicherte Lesezeichen bearbeitet und umgeordnet werden.</p>
</body>
</html>
import android.net.Uri;
import android.net.http.SslCertificate;
import android.net.http.SslError;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import com.stoutner.privacybrowser.R;
import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
-import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
-import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
-import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
import com.stoutner.privacybrowser.asynctasks.SaveUrl;
import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
-import com.stoutner.privacybrowser.dataclasses.PendingDialog;
+import com.stoutner.privacybrowser.coroutines.GetHostIpAddressesCoroutine;
+import com.stoutner.privacybrowser.coroutines.PopulateBlocklistsCoroutine;
+import com.stoutner.privacybrowser.coroutines.PrepareSaveDialogCoroutine;
+import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener,
- PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
+ PopulateBlocklistsCoroutine.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
// Define the public static variables.
public static final ExecutorService executorService = Executors.newFixedThreadPool(4);
public static String orbotStatus = "unknown";
- public static final ArrayList<PendingDialog> pendingDialogsArrayList = new ArrayList<>();
+ public static final ArrayList<PendingDialogDataClass> pendingDialogsArrayList = new ArrayList<>();
public static String proxyMode = ProxyHelper.NONE;
// Declare the public static variables.
private int savedTabPosition;
private String savedProxyMode;
- // Define the class variables.
- @SuppressWarnings("rawtypes")
- AsyncTask populateBlocklists;
-
// The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
// `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
private NestedScrollWebView currentWebView;
// Register the on back pressed callback.
getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
+ // Instantiate the populate blocklists coroutine.
+ PopulateBlocklistsCoroutine populateBlocklistsCoroutine = new PopulateBlocklistsCoroutine(this);
+
// Populate the blocklists.
- populateBlocklists = new PopulateBlocklists(this, this).execute();
+ populateBlocklistsCoroutine.populateBlocklists(this);
}
@Override
// Show any pending dialogs.
for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
// Get the pending dialog from the array list.
- PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
+ PendingDialogDataClass pendingDialogDataClass = pendingDialogsArrayList.get(i);
// Show the pending dialog.
- pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
+ pendingDialogDataClass.dialogFragment.show(getSupportFragmentManager(), pendingDialogDataClass.tag);
}
// Clear the pending dialogs array list.
bookmarksDatabaseHelper.close();
}
- // Stop populating the blocklists if the AsyncTask is running in the background.
- if (populateBlocklists != null) {
- populateBlocklists.cancel(true);
- }
-
// Run the default commands.
super.onDestroy();
}
downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
} else { // Handle the download inside of Privacy Browser.
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
- currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
+ PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
+ currentWebView.getAcceptCookies());
}
// Consume the event.
downloadUrlWithExternalApp(linkUrl);
} else { // Handle the download inside of Privacy Browser.
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
- currentWebView.getAcceptCookies()).execute(linkUrl);
+ PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
}
// Consume the event.
downloadUrlWithExternalApp(imageUrl);
} else { // Handle the download inside of Privacy Browser.
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
- currentWebView.getAcceptCookies()).execute(imageUrl);
+ PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
}
// Consume the event.
downloadUrlWithExternalApp(imageUrl);
} else { // Handle the download inside of Privacy Browser.
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
- currentWebView.getAcceptCookies()).execute(imageUrl);
+ PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
}
// Consume the event.
downloadUrlWithExternalApp(linkUrl);
} else { // Handle the download inside of Privacy Browser.
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
- currentWebView.getAcceptCookies()).execute(linkUrl);
+ PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
}
// Consume the event.
waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
} catch (Exception waitingForTorException) {
// Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
- pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
+ pendingDialogsArrayList.add(new PendingDialogDataClass(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
}
}
}
orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
} catch (Exception orbotNotInstalledException) {
// Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
- pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+ pendingDialogsArrayList.add(new PendingDialogDataClass(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
}
}
}
i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
} catch (Exception i2pNotInstalledException) {
// Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
- pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+ pendingDialogsArrayList.add(new PendingDialogDataClass(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
}
}
}
}
// Get the file name from the content disposition.
- String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
+ String fileNameString = PrepareSaveDialogCoroutine.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
// Instantiate the save dialog.
DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, formattedFileSizeString, fileNameString, userAgent,
saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
} catch (Exception exception) { // The dialog could not be shown.
// Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
- pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog)));
+ pendingDialogsArrayList.add(new PendingDialogDataClass(saveDialogFragment, getString(R.string.save_dialog)));
}
}
});
// Get a URI for the current URL.
Uri currentUri = Uri.parse(url);
- // Get the IP addresses for the host.
- new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
+ // Get the current domain name.
+ String currentDomainName = currentUri.getHost();
+
+ if ((currentDomainName != null) && !currentDomainName.isEmpty()) {
+ // Get the IP addresses for the current URI.
+ GetHostIpAddressesCoroutine.getAddresses(currentDomainName, nestedScrollWebView, getSupportFragmentManager(), getString(R.string.pinned_mismatch));
+ }
// Replace Refresh with Stop if the options menu has been created. (The first WebView typically begins loading before the menu items are instantiated.)
if (optionsMenu != null) {
sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
} catch (Exception exception) {
// Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
- pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
+ pendingDialogsArrayList.add(new PendingDialogDataClass(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
}
}
}
/*
- * Copyright © 2016-2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2016-2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
*
import android.widget.TextView
import com.stoutner.privacybrowser.R
-import com.stoutner.privacybrowser.dataclasses.History
+import com.stoutner.privacybrowser.dataclasses.HistoryDataClass
import java.util.ArrayList
-class HistoryArrayAdapter(context: Context, historyArrayList: ArrayList<History>, private val currentPageId: Int) : ArrayAdapter<History>(context, 0, historyArrayList) {
+class HistoryArrayAdapter(context: Context, historyDataClassArrayList: ArrayList<HistoryDataClass>, private val currentPageId: Int) : ArrayAdapter<HistoryDataClass>(context, 0, historyDataClassArrayList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
// Initialize a populated view from the convert view.
var populatedView = convertView
// Return the populated view.
return populatedView
}
-}
\ No newline at end of file
+}
+++ /dev/null
-/*
- * Copyright © 2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.os.AsyncTask;
-
-import androidx.fragment.app.FragmentManager;
-
-import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
-import com.stoutner.privacybrowser.views.NestedScrollWebView;
-
-import java.lang.ref.WeakReference;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-// This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `String` contains the results.
-public class GetHostIpAddresses extends AsyncTask<String, Void, String> {
- // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
- private final WeakReference<Activity> activityWeakReference;
- private final WeakReference<FragmentManager> fragmentManagerWeakReference;
- private final WeakReference<NestedScrollWebView> nestedScrollWebViewWeakReference;
-
- public GetHostIpAddresses(Activity activity, FragmentManager fragmentManager, NestedScrollWebView nestedScrollWebView) {
- // Populate the weak references.
- activityWeakReference = new WeakReference<>(activity);
- fragmentManagerWeakReference = new WeakReference<>(fragmentManager);
- nestedScrollWebViewWeakReference = new WeakReference<>(nestedScrollWebView);
- }
-
- @Override
- protected String doInBackground(String... domainName) {
- // Get a handles for the weak references.
- Activity activity = activityWeakReference.get();
- FragmentManager fragmentManager = fragmentManagerWeakReference.get();
- NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
-
- // Abort if the activity or its components are gone.
- if ((activity == null) || activity.isFinishing() || fragmentManager == null || nestedScrollWebView == null) {
- // Return an empty spannable string builder.
- return "";
- }
-
- // Initialize an IP address string builder.
- StringBuilder ipAddresses = new StringBuilder();
-
- // Get an array with the IP addresses for the host.
- try {
- // Get an array with all the IP addresses for the domain.
- InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
-
- // Add each IP address to the string builder.
- for (InetAddress inetAddress : inetAddressesArray) {
- // Add a line break to the string builder if this is not the first IP address.
- if (ipAddresses.length() > 0) {
- ipAddresses.append("\n");
- }
-
- // Add the IP address to the string builder.
- ipAddresses.append(inetAddress.getHostAddress());
- }
- } catch (UnknownHostException exception) {
- // Do nothing.
- }
-
- // Return the string.
- return ipAddresses.toString();
- }
-
- // `onPostExecute()` operates on the UI thread.
- @Override
- protected void onPostExecute(String ipAddresses) {
- // Get a handle for the activity and the nested scroll WebView.
- Activity activity = activityWeakReference.get();
- FragmentManager fragmentManager = fragmentManagerWeakReference.get();
- NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
-
- // Abort if the activity or its components are gone.
- if ((activity == null) || activity.isFinishing() || fragmentManager == null || nestedScrollWebView == null) {
- return;
- }
-
- // Store the IP addresses.
- nestedScrollWebView.setCurrentIpAddresses(ipAddresses);
-
- // Checked for pinned mismatches if there is pinned information and it is not ignored.
- if ((nestedScrollWebView.hasPinnedSslCertificate() || !nestedScrollWebView.getPinnedIpAddresses().equals("")) && !nestedScrollWebView.getIgnorePinnedDomainInformation()) {
- CheckPinnedMismatchHelper.checkPinnedMismatch(activity, fragmentManager, nestedScrollWebView);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright © 2020-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.webkit.CookieManager;
-import android.widget.TextView;
-
-import androidx.appcompat.app.AlertDialog;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.helpers.ProxyHelper;
-
-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 weak references for the calling context and alert dialog.
- private final WeakReference<Context> contextWeakReference;
- private final WeakReference<AlertDialog> alertDialogWeakReference;
-
- // Define the class variables.
- private final String userAgent;
- private final boolean cookiesEnabled;
-
- // The public constructor.
- public GetUrlSize(Context context, AlertDialog alertDialog, String userAgent, boolean cookiesEnabled) {
- // Populate the week references for the context and alert dialog.
- 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 must catch exceptions.
- 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) {
- // Get 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. This initiates a network connection.
- 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.invalid_url);
- } else { // The response code is not an error message.
- // Get the content length header.
- String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
-
- // Only process the content length string if it isn't null.
- if (contentLengthString != null) {
- // Convert the content length string to a long.
- 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 (Exception exception) {
- // Set the formatted file size to indicate a bad URL.
- formattedFileSize = context.getString(R.string.invalid_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 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);
-
- // Remove the incorrect warning below that the file size text view might be null.
- assert fileSizeTextView != null;
-
- // Update the file size.
- fileSizeTextView.setText(fileSize);
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright © 2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.view.View;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.drawerlayout.widget.DrawerLayout;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.helpers.BlocklistHelper;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-public class PopulateBlocklists extends AsyncTask<Void, String, ArrayList<ArrayList<List<String[]>>>> {
- // The public interface is used to send information back to the parent activity.
- public interface PopulateBlocklistsListener {
- void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists);
- }
-
- // Define a populate blocklists listener.
- private final PopulateBlocklistsListener populateBlocklistsListener;
-
- // Define weak references for the activity and context.
- private final WeakReference<Context> contextWeakReference;
- private final WeakReference<Activity> activityWeakReference;
-
- // The public constructor.
- public PopulateBlocklists(Context context, Activity activity) {
- // Populate the weak reference to the context.
- contextWeakReference = new WeakReference<>(context);
-
- // Populate the weak reference to the activity.
- activityWeakReference = new WeakReference<>(activity);
-
- // Get a handle for the populate blocklists listener from the launching activity.
- populateBlocklistsListener = (PopulateBlocklistsListener) context;
- }
-
- // `onPreExecute()` operates on the UI thread.
- @Override
- protected void onPreExecute() {
- // Get a handle for the activity.
- Activity activity = activityWeakReference.get();
-
- // Abort if the activity is gone.
- if ((activity == null) || activity.isFinishing()) {
- return;
- }
-
- // Get handles for the views.
- RelativeLayout loadingBlocklistsRelativeLayout = activity.findViewById(R.id.loading_blocklists_relativelayout);
-
- // Show the loading blocklists screen.
- loadingBlocklistsRelativeLayout.setVisibility(View.VISIBLE);
- }
-
- @Override
- protected ArrayList<ArrayList<List<String[]>>> doInBackground(Void... none) {
- // Exit the AsyncTask if the app has been restarted.
- if (isCancelled()) {
- return null;
- }
-
- // Get a handle for the context.
- Context context = contextWeakReference.get();
-
- // Instantiate the blocklist helper.
- BlocklistHelper blocklistHelper = new BlocklistHelper();
-
- // Create a combined array list.
- ArrayList<ArrayList<List<String[]>>> combinedBlocklists = new ArrayList<>();
-
- // Load the blocklists if the context still exists.
- if (context != null) {
- // Update the progress.
- publishProgress(context.getString(R.string.loading_easylist));
-
- // Populate EasyList.
- ArrayList<List<String[]>> easyList = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/easylist.txt");
-
- // Exit the AsyncTask if the app has been restarted.
- if (isCancelled()) {
- return null;
- }
-
-
- // Update the progress.
- publishProgress(context.getString(R.string.loading_easyprivacy));
-
- // Populate EasyPrivacy.
- ArrayList<List<String[]>> easyPrivacy = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/easyprivacy.txt");
-
- // Exit the AsyncTask if the app has been restarted.
- if (isCancelled()) {
- return null;
- }
-
-
-
- // Update the progress.
- publishProgress(context.getString(R.string.loading_fanboys_annoyance_list));
-
- // Populate Fanboy's Annoyance List.
- ArrayList<List<String[]>> fanboysAnnoyanceList = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/fanboy-annoyance.txt");
-
- // Exit the AsyncTask if the app has been restarted.
- if (isCancelled()) {
- return null;
- }
-
-
-
- // Update the progress.
- publishProgress(context.getString(R.string.loading_fanboys_social_blocking_list));
-
- // Populate Fanboy's Social Blocking List.
- ArrayList<List<String[]>> fanboysSocialList = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/fanboy-social.txt");
-
- // Exit the AsyncTask if the app has been restarted.
- if (isCancelled()) {
- return null;
- }
-
-
-
- // Update the progress.
- publishProgress(context.getString(R.string.loading_ultralist));
-
- // Populate UltraList.
- ArrayList<List<String[]>> ultraList = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/ultralist.txt");
-
- // Exit the AsyncTask if the app has been restarted.
- if (isCancelled()) {
- return null;
- }
-
-
-
- // Update the progress.
- publishProgress(context.getString(R.string.loading_ultraprivacy));
-
- // Populate UltraPrivacy.
- ArrayList<List<String[]>> ultraPrivacy = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/ultraprivacy.txt");
-
- // Exit the AsyncTask if the app has been restarted.
- if (isCancelled()) {
- return null;
- }
-
-
-
- // Populate the combined array list.
- combinedBlocklists.add(easyList);
- combinedBlocklists.add(easyPrivacy);
- combinedBlocklists.add(fanboysAnnoyanceList);
- combinedBlocklists.add(fanboysSocialList);
- combinedBlocklists.add(ultraList);
- combinedBlocklists.add(ultraPrivacy);
- }
-
- // Return the combined array list.
- return combinedBlocklists;
- }
-
- @Override
- protected void onProgressUpdate(String... loadingBlocklist) {
- // Get a handle for the activity.
- Activity activity = activityWeakReference.get();
-
- // Abort if the activity is gone.
- if ((activity == null) || activity.isFinishing()) {
- return;
- }
-
- // Get a handle for the loading blocklist text view.
- TextView loadingBlocklistTextView = activity.findViewById(R.id.loading_blocklist_textview);
-
- // Update the status.
- loadingBlocklistTextView.setText(loadingBlocklist[0]);
- }
-
- @Override
- protected void onPostExecute(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
- // Get a handle for the activity.
- Activity activity = activityWeakReference.get();
-
- // Abort if the activity is gone.
- if ((activity == null) || activity.isFinishing()) {
- return;
- }
-
- // Get handles for the views.
- DrawerLayout drawerLayout = activity.findViewById(R.id.drawerlayout);
- RelativeLayout loadingBlocklistsRelativeLayout = activity.findViewById(R.id.loading_blocklists_relativelayout);
-
- // Show the drawer layout.
- drawerLayout.setVisibility(View.VISIBLE);
-
- // Hide the loading blocklists screen.
- loadingBlocklistsRelativeLayout.setVisibility(View.GONE);
-
- // Enable the sliding drawers.
- drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
- // Add the first tab.
- populateBlocklistsListener.finishedPopulatingBlocklists(combinedBlocklists);
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright © 2020-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.content.Context;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.webkit.CookieManager;
-import android.webkit.MimeTypeMap;
-
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentManager;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.activities.MainWebViewActivity;
-import com.stoutner.privacybrowser.dataclasses.PendingDialog;
-import com.stoutner.privacybrowser.dialogs.SaveDialog;
-import com.stoutner.privacybrowser.helpers.ProxyHelper;
-
-import java.lang.ref.WeakReference;
-import java.net.HttpURLConnection;
-import java.net.Proxy;
-import java.net.URL;
-import java.text.NumberFormat;
-
-public class PrepareSaveDialog extends AsyncTask<String, Void, String[]> {
- // Define weak references.
- private final WeakReference<Activity> activityWeakReference;
- private final WeakReference<Context> contextWeakReference;
- private final WeakReference<FragmentManager> fragmentManagerWeakReference;
-
- // Define the class variables.
- private final String userAgent;
- private final boolean cookiesEnabled;
- private String urlString;
-
- // The public constructor.
- public PrepareSaveDialog(Activity activity, Context context, FragmentManager fragmentManager, String userAgent, boolean cookiesEnabled) {
- // Populate the weak references.
- activityWeakReference = new WeakReference<>(activity);
- contextWeakReference = new WeakReference<>(context);
- fragmentManagerWeakReference = new WeakReference<>(fragmentManager);
-
- // Store the class variables.
- this.userAgent = userAgent;
- this.cookiesEnabled = cookiesEnabled;
- }
-
- @Override
- protected String[] doInBackground(String... urlToSave) {
- // Get a handle for the activity and context.
- Activity activity = activityWeakReference.get();
- Context context = contextWeakReference.get();
-
- // Abort if the activity is gone.
- if (activity == null || activity.isFinishing()) {
- // Return a null string array.
- return null;
- }
-
- // Get the URL string.
- urlString = urlToSave[0];
-
- // Define the strings.
- String formattedFileSize;
- String fileNameString;
-
- // Populate the file size and name strings.
- if (urlString.startsWith("data:")) { // The URL contains the entire data of an image.
- // Remove `data:` from the beginning of the URL.
- String urlWithoutData = urlString.substring(5);
-
- // Get the URL MIME type, which ends with a `;`.
- String urlMimeType = urlWithoutData.substring(0, urlWithoutData.indexOf(";"));
-
- // Get the Base64 data, which begins after a `,`.
- String base64DataString = urlWithoutData.substring(urlWithoutData.indexOf(",") + 1);
-
- // Calculate the file size of the data URL. Each Base64 character represents 6 bits.
- formattedFileSize = NumberFormat.getInstance().format(base64DataString.length() * 3L / 4) + " " + context.getString(R.string.bytes);
-
- // Set the file name according to the MIME type.
- fileNameString = context.getString(R.string.file) + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(urlMimeType);
- } else { // The URL refers to the location of the data.
- // Initialize the formatted file size string.
- formattedFileSize = context.getString(R.string.unknown_size);
-
- // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
- try {
- // Convert the URL string to a URL.
- URL url = new URL(urlString);
-
- // 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) {
- // Get 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 {
- // Get the status code. This initiates a network connection.
- int responseCode = httpUrlConnection.getResponseCode();
-
- // 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.invalid_url);
-
- // Set the file name according to the URL.
- fileNameString = getFileNameFromUrl(context, urlString, null);
- } else { // The response code is not an error message.
- // Get the headers.
- String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
- String contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition");
- String contentTypeString = httpUrlConnection.getContentType();
-
- // Remove anything after the MIME type in the content type string.
- if (contentTypeString.contains(";")) {
- // Remove everything beginning with the `;`.
- contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";"));
- }
-
- // Only process the content length string if it isn't null.
- if (contentLengthString != null) {
- // Convert the content length string to a long.
- long fileSize = Long.parseLong(contentLengthString);
-
- // Format the file size.
- formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes);
- }
-
- // Get the file name string from the content disposition.
- fileNameString = getFileNameFromHeaders(context, contentDispositionString, contentTypeString, urlString);
- }
- } finally {
- // Disconnect the HTTP URL connection.
- httpUrlConnection.disconnect();
- }
- } catch (Exception exception) {
- // Set the formatted file size to indicate a bad URL.
- formattedFileSize = context.getString(R.string.invalid_url);
-
- // Set the file name according to the URL.
- fileNameString = getFileNameFromUrl(context, urlString, null);
- }
- }
-
- // Return the formatted file size and name as a string array.
- return new String[] {formattedFileSize, fileNameString};
- }
-
- // `onPostExecute()` operates on the UI thread.
- @Override
- protected void onPostExecute(String[] fileStringArray) {
- // Get a handle for the activity and the fragment manager.
- Activity activity = activityWeakReference.get();
- FragmentManager fragmentManager = fragmentManagerWeakReference.get();
-
- // Abort if the activity is gone.
- if (activity == null || activity.isFinishing()) {
- // Exit.
- return;
- }
-
- // Instantiate the save dialog.
- DialogFragment saveDialogFragment = SaveDialog.saveUrl(urlString, fileStringArray[0], fileStringArray[1], userAgent, cookiesEnabled);
-
- // Try to show the dialog. Sometimes the window is not active.
- try {
- // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
- saveDialogFragment.show(fragmentManager, activity.getString(R.string.save_dialog));
- } catch (Exception exception) {
- // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
- MainWebViewActivity.pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, activity.getString(R.string.save_dialog)));
- }
- }
-
- // Content dispositions can contain other text besides the file name, and they can be in any order.
- // Elements are separated by semicolons. Sometimes the file names are contained in quotes.
- public static String getFileNameFromHeaders(Context context, String contentDispositionString, String contentTypeString, String urlString) {
- // Define a file name string.
- String fileNameString;
-
- // Only process the content disposition string if it isn't null.
- if (contentDispositionString != null) { // The content disposition is not null.
- // Check to see if the content disposition contains a file name.
- if (contentDispositionString.contains("filename=")) { // The content disposition contains a filename.
- // Get the part of the content disposition after `filename=`.
- fileNameString = contentDispositionString.substring(contentDispositionString.indexOf("filename=") + 9);
-
- // Remove any `;` and anything after it. This removes any entries after the filename.
- if (fileNameString.contains(";")) {
- // Remove the first `;` and everything after it.
- fileNameString = fileNameString.substring(0, fileNameString.indexOf(";") - 1);
- }
-
- // Remove any `"` at the beginning of the string.
- if (fileNameString.startsWith("\"")) {
- // Remove the first character.
- fileNameString = fileNameString.substring(1);
- }
-
- // Remove any `"` at the end of the string.
- if (fileNameString.endsWith("\"")) {
- // Remove the last character.
- fileNameString = fileNameString.substring(0, fileNameString.length() - 1);
- }
- } else { // The headers contain no useful information.
- // Get the file name string from the URL.
- fileNameString = getFileNameFromUrl(context, urlString, contentTypeString);
- }
- } else { // The content disposition is null.
- // Get the file name string from the URL.
- fileNameString = getFileNameFromUrl(context, urlString, contentTypeString);
- }
-
- // Return the file name string.
- return fileNameString;
- }
-
- private static String getFileNameFromUrl(Context context, String urlString, String contentTypeString) {
- // Convert the URL string to a URI.
- Uri uri = Uri.parse(urlString);
-
- // 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 = context.getString(R.string.file);
-
- if (MimeTypeMap.getSingleton().hasMimeType(contentTypeString)) { // The content type contains a MIME type.
- // Add the file extension that matches the MIME type.
- lastPathSegment = lastPathSegment + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(contentTypeString);
- }
- }
-
- // Return the last path segment as the file name.
- return lastPathSegment;
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.provider.OpenableColumns;
-import android.widget.LinearLayout;
-
-import com.google.android.material.snackbar.Snackbar;
-
-import com.stoutner.privacybrowser.R;
-
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.lang.ref.WeakReference;
-
-public class SaveAboutVersionImage extends AsyncTask<Void, Void, String> {
- // Declare the class constants.
- private final String SUCCESS = "Success";
-
- // Declare the weak references.
- private final WeakReference<Activity> activityWeakReference;
- private final WeakReference<LinearLayout> aboutVersionLinearLayoutWeakReference;
-
- // Declare the class variables.
- private Snackbar savingImageSnackbar;
- private Bitmap aboutVersionBitmap;
- private final Uri fileUri;
- private final String fileNameString;
-
- // The public constructor.
- public SaveAboutVersionImage(Activity activity, Uri fileUri, LinearLayout aboutVersionLinearLayout) {
- // Populate the weak references.
- activityWeakReference = new WeakReference<>(activity);
- aboutVersionLinearLayoutWeakReference = new WeakReference<>(aboutVersionLinearLayout);
-
- // Store the class variables.
- this.fileUri = fileUri;
-
- // Query the exact file name if the API >= 26.
- if (Build.VERSION.SDK_INT >= 26) {
- // Get a cursor from the content resolver.
- Cursor contentResolverCursor = activity.getContentResolver().query(fileUri, null, null, null);
-
- // Move to the first row.
- contentResolverCursor.moveToFirst();
-
- // Get the file name from the cursor.
- fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
-
- // Close the cursor.
- contentResolverCursor.close();
- } else {
- // Use the URI last path segment as the file name string.
- fileNameString = fileUri.getLastPathSegment();
- }
- }
-
- // `onPreExecute()` operates on the UI thread.
- @Override
- protected void onPreExecute() {
- // Get handles for the activity and the linear layout.
- Activity activity = activityWeakReference.get();
- LinearLayout aboutVersionLinearLayout = aboutVersionLinearLayoutWeakReference.get();
-
- // Abort if the activity or the linear layout is gone.
- if ((activity == null) || activity.isFinishing() || aboutVersionLinearLayout == null) {
- return;
- }
-
- // Create a saving image snackbar.
- savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image) + " " + fileNameString, Snackbar.LENGTH_INDEFINITE);
-
- // Display the saving image snackbar.
- savingImageSnackbar.show();
-
- // Create the about version bitmap. This can be replaced by PixelCopy once the minimum API >= 26.
- // Once the Minimum API >= 26 Bitmap.Config.RBGA_F16 can be used instead of ARGB_8888. The linear layout commands must be run on the UI thread.
- aboutVersionBitmap = Bitmap.createBitmap(aboutVersionLinearLayout.getWidth(), aboutVersionLinearLayout.getHeight(), Bitmap.Config.ARGB_8888);
-
- // Create a canvas.
- Canvas aboutVersionCanvas = new Canvas(aboutVersionBitmap);
-
- // Draw the current about version onto the bitmap. The linear layout commands must be run on the UI thread.
- aboutVersionLinearLayout.draw(aboutVersionCanvas);
- }
-
- @Override
- protected String doInBackground(Void... Void) {
- // Get a handle for the activity.
- Activity activity = activityWeakReference.get();
-
- // Abort if the activity is gone.
- if (((activity == null) || activity.isFinishing())) {
- return "";
- }
-
- // Create an about version PNG byte array output stream.
- ByteArrayOutputStream aboutVersionByteArrayOutputStream = new ByteArrayOutputStream();
-
- // Convert the bitmap to a PNG. `0` is for lossless compression (the only option for a PNG). This compression takes a long time. Once the minimum API >= 30 this could be replaced with WEBP_LOSSLESS.
- aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream);
-
- // Create a file creation disposition string.
- String fileCreationDisposition = SUCCESS;
-
- try {
- // Open an output stream.
- OutputStream outputStream = activity.getContentResolver().openOutputStream(fileUri);
-
- // Write the webpage image to the image file.
- aboutVersionByteArrayOutputStream.writeTo(outputStream);
-
- // Close the output stream.
- outputStream.close();
- } catch (Exception exception) {
- // Store the error in the file creation disposition string.
- fileCreationDisposition = exception.toString();
- }
-
- // return the file creation disposition string.
- return fileCreationDisposition;
- }
-
- // `onPostExecute()` operates on the UI thread.
- @Override
- protected void onPostExecute(String fileCreationDisposition) {
- // Get handles for the weak references.
- Activity activity = activityWeakReference.get();
- LinearLayout aboutVersionLinearLayout = aboutVersionLinearLayoutWeakReference.get();
-
- // Abort if the activity is gone.
- if ((activity == null) || activity.isFinishing()) {
- return;
- }
-
- // Dismiss the saving image snackbar.
- savingImageSnackbar.dismiss();
-
- // Display a file creation disposition snackbar.
- if (fileCreationDisposition.equals(SUCCESS)) {
- // Create a file saved snackbar.
- Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show();
- } else {
- Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file, fileNameString, fileCreationDisposition), Snackbar.LENGTH_INDEFINITE).show();
- }
- }
-}
--- /dev/null
+/*
+ * Copyright 2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.coroutines
+
+import androidx.fragment.app.FragmentManager
+import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper
+import com.stoutner.privacybrowser.views.NestedScrollWebView
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.lang.StringBuilder
+import java.net.InetAddress
+import java.net.UnknownHostException
+
+object GetHostIpAddressesCoroutine {
+ @JvmStatic
+ fun getAddresses(domainName: String, nestedScrollWebView: NestedScrollWebView, supportFragmentManager: FragmentManager, pinnedMismatchString: String) {
+ // Get the IP addresses using a coroutine.
+ CoroutineScope(Dispatchers.Main).launch {
+ // Get the IP addresses on the IO thread.
+ withContext(Dispatchers.IO) {
+ // Get an array with the IP addresses for the host.
+ try {
+ // Initialize an IP address string builder.
+ val ipAddresses = StringBuilder()
+
+ // Get an array with all the IP addresses for the domain.
+ val inetAddressesArray = InetAddress.getAllByName(domainName)
+
+ // Add each IP address to the string builder.
+ for (inetAddress in inetAddressesArray) {
+ // Add a line break to the string builder if this is not the first IP address.
+ if (ipAddresses.isNotEmpty()) {
+ ipAddresses.append("\n")
+ }
+
+ // Add the IP address to the string builder.
+ ipAddresses.append(inetAddress.hostAddress)
+ }
+
+ // Store the IP addresses.
+ nestedScrollWebView.currentIpAddresses = ipAddresses.toString()
+ } catch (exception: UnknownHostException) {
+ // Do nothing.
+ }
+ }
+ }
+
+ // Checked for pinned mismatches if there is pinned information and it is not ignored.
+ if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.pinnedIpAddresses != "") && !nestedScrollWebView.ignorePinnedDomainInformation) {
+ CheckPinnedMismatchHelper.checkPinnedMismatch(nestedScrollWebView, supportFragmentManager, pinnedMismatchString)
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.coroutines
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.drawerlayout.widget.DrawerLayout
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.helpers.BlocklistHelper
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.util.ArrayList
+
+class PopulateBlocklistsCoroutine(context: Context) {
+ // The public interface is used to send information back to the parent activity.
+ interface PopulateBlocklistsListener {
+ fun finishedPopulatingBlocklists(combinedBlocklists: ArrayList<ArrayList<List<Array<String>>>>)
+ }
+
+ // Define a populate blocklists listener.
+ private val populateBlocklistsListener: PopulateBlocklistsListener
+
+ // Define the class variables.
+ private val context: Context
+
+ // The public constructor.
+ init {
+ // Get a handle for the populate blocklists listener from the launching activity.
+ populateBlocklistsListener = context as PopulateBlocklistsListener
+
+ // Store the context.
+ this.context = context
+ }
+
+ fun populateBlocklists(activity: Activity) {
+ // Use a coroutine to populate the blocklists.
+ CoroutineScope(Dispatchers.Main).launch {
+ // Get handles for the views.
+ val drawerLayout = activity.findViewById<DrawerLayout>(R.id.drawerlayout)
+ val loadingBlocklistsRelativeLayout = activity.findViewById<RelativeLayout>(R.id.loading_blocklists_relativelayout)
+ val loadingBlocklistTextView = activity.findViewById<TextView>(R.id.loading_blocklist_textview)
+
+ // Show the loading blocklists screen.
+ loadingBlocklistsRelativeLayout.visibility = View.VISIBLE
+
+ // Instantiate the blocklist helper.
+ val blocklistHelper = BlocklistHelper()
+
+ // Create a combined array list.
+ val combinedBlocklists = ArrayList<ArrayList<List<Array<String>>>>()
+
+ // Advertise the loading of EasyList.
+ loadingBlocklistTextView.text = context.getString(R.string.loading_easylist)
+
+ withContext(Dispatchers.IO) {
+ // Populate EasyList.
+ val easyList = blocklistHelper.parseBlocklist(context.assets, "blocklists/easylist.txt")
+
+ // Advertise the loading of EasyPrivacy.
+ withContext(Dispatchers.Main) {
+ loadingBlocklistTextView.text = context.getString(R.string.loading_easyprivacy)
+ }
+
+ // Populate EasyPrivacy.
+ val easyPrivacy = blocklistHelper.parseBlocklist(context.assets, "blocklists/easyprivacy.txt")
+
+ // Advertise the loading of Fanboy's Annoyance List.
+ withContext(Dispatchers.Main) {
+ loadingBlocklistTextView.text = context.getString(R.string.loading_fanboys_annoyance_list)
+ }
+
+ // Populate Fanboy's Annoyance List.
+ val fanboysAnnoyanceList = blocklistHelper.parseBlocklist(context.assets, "blocklists/fanboy-annoyance.txt")
+
+ // Advertise the loading of Fanboy's social blocking list.
+ withContext(Dispatchers.Main) {
+ loadingBlocklistTextView.text = context.getString(R.string.loading_fanboys_social_blocking_list)
+ }
+
+ // Populate Fanboy's Social Blocking List.
+ val fanboysSocialList = blocklistHelper.parseBlocklist(context.assets, "blocklists/fanboy-social.txt")
+
+ // Advertise the loading of UltraList
+ withContext(Dispatchers.Main) {
+ loadingBlocklistTextView.text = context.getString(R.string.loading_ultralist)
+ }
+
+ // Populate UltraList.
+ val ultraList = blocklistHelper.parseBlocklist(context.assets, "blocklists/ultralist.txt")
+
+ // Advertise the loading of UltraPrivacy.
+ withContext(Dispatchers.Main) {
+ loadingBlocklistTextView.text = context.getString(R.string.loading_ultraprivacy)
+ }
+
+ // Populate UltraPrivacy.
+ val ultraPrivacy = blocklistHelper.parseBlocklist(context.assets, "blocklists/ultraprivacy.txt")
+
+ // Populate the combined array list.
+ combinedBlocklists.add(easyList)
+ combinedBlocklists.add(easyPrivacy)
+ combinedBlocklists.add(fanboysAnnoyanceList)
+ combinedBlocklists.add(fanboysSocialList)
+ combinedBlocklists.add(ultraList)
+ combinedBlocklists.add(ultraPrivacy)
+
+ // Update the UI.
+ withContext(Dispatchers.Main) {
+ // Show the drawer layout.
+ drawerLayout.visibility = View.VISIBLE
+
+ // Hide the loading blocklists screen.
+ loadingBlocklistsRelativeLayout.visibility = View.GONE
+
+ // Enable the sliding drawers.
+ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
+
+ // Add the first tab.
+ populateBlocklistsListener.finishedPopulatingBlocklists(combinedBlocklists)
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.coroutines
+
+import android.content.Context
+import android.net.Uri
+import android.webkit.CookieManager
+import android.webkit.MimeTypeMap
+
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.activities.MainWebViewActivity
+import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass
+import com.stoutner.privacybrowser.dialogs.SaveDialog
+import com.stoutner.privacybrowser.helpers.ProxyHelper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.lang.Exception
+import java.net.HttpURLConnection
+import java.net.URL
+import java.text.NumberFormat
+
+object PrepareSaveDialogCoroutine {
+ @JvmStatic
+ fun prepareSaveDialog(context: Context, supportFragmentManager: FragmentManager, urlString: String, userAgent: String, cookiesEnabled: Boolean) {
+ // Use a coroutine to prepare the save dialog.
+ CoroutineScope(Dispatchers.Main).launch {
+ // Make the network requests on the IO thread.
+ withContext(Dispatchers.IO) {
+ // Define the strings.
+ var formattedFileSize: String
+ var fileNameString: String
+
+ // Populate the file size and name strings.
+ if (urlString.startsWith("data:")) { // The URL contains the entire data of an image.
+ // Remove `data:` from the beginning of the URL.
+ val urlWithoutData = urlString.substring(5)
+
+ // Get the URL MIME type, which ends with a `;`.
+ val urlMimeType = urlWithoutData.substring(0, urlWithoutData.indexOf(";"))
+
+ // Get the Base64 data, which begins after a `,`.
+ val base64DataString = urlWithoutData.substring(urlWithoutData.indexOf(",") + 1)
+
+ // Calculate the file size of the data URL. Each Base64 character represents 6 bits.
+ formattedFileSize = NumberFormat.getInstance().format(base64DataString.length * 3L / 4) + " " + context.getString(R.string.bytes)
+
+ // Set the file name according to the MIME type.
+ fileNameString = context.getString(R.string.file) + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(urlMimeType)
+ } else { // The URL refers to the location of the data.
+ // Initialize the formatted file size string.
+ formattedFileSize = context.getString(R.string.unknown_size)
+
+ // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
+ try {
+ // Convert the URL string to a URL.
+ val url = URL(urlString)
+
+ // Instantiate the proxy helper.
+ val proxyHelper = ProxyHelper()
+
+ // Get the current proxy.
+ val proxy = proxyHelper.getCurrentProxy(context)
+
+ // Open a connection to the URL. No data is actually sent at this point.
+ val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
+
+ // Add the user agent to the header property.
+ httpUrlConnection.setRequestProperty("User-Agent", userAgent)
+
+ // Add the cookies if they are enabled.
+ if (cookiesEnabled) {
+ // Get the cookies for the current domain.
+ val cookiesString = CookieManager.getInstance().getCookie(url.toString())
+
+ // Add the cookies if they are not null.
+ if (cookiesString != null)
+ 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 {
+ // Get the status code. This initiates a network connection.
+ val responseCode = httpUrlConnection.responseCode
+
+ // 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.invalid_url)
+
+ // Set the file name according to the URL.
+ fileNameString = getFileNameFromUrl(context, urlString, null)
+ } else { // The response code is not an error message.
+ // Get the headers.
+ val contentLengthString = httpUrlConnection.getHeaderField("Content-Length")
+ val contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition")
+ var contentTypeString = httpUrlConnection.contentType
+
+ // Remove anything after the MIME type in the content type string.
+ if (contentTypeString.contains(";"))
+ contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";"))
+
+ // Only process the content length string if it isn't null.
+ if (contentLengthString != null) {
+ // Convert the content length string to a long.
+ val fileSize = contentLengthString.toLong()
+
+ // Format the file size.
+ formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
+ }
+
+ // Get the file name string from the content disposition.
+ fileNameString = getFileNameFromHeaders(context, contentDispositionString, contentTypeString, urlString)
+ }
+ } finally {
+ // Disconnect the HTTP URL connection.
+ httpUrlConnection.disconnect()
+ }
+ } catch (exception: Exception) {
+ // Set the formatted file size to indicate a bad URL.
+ formattedFileSize = context.getString(R.string.invalid_url)
+
+ // Set the file name according to the URL.
+ fileNameString = getFileNameFromUrl(context, urlString, null)
+ }
+ }
+
+ // Display the dialog on the main thread.
+ withContext(Dispatchers.Main) {
+ // Instantiate the save dialog.
+ val saveDialogFragment: DialogFragment = SaveDialog.saveUrl(urlString, formattedFileSize, fileNameString, userAgent, cookiesEnabled)
+
+ // Try to show the dialog. Sometimes the window is not active.
+ try {
+ // Show the save dialog.
+ saveDialogFragment.show(supportFragmentManager, context.getString(R.string.save_dialog))
+ } catch (exception: Exception) {
+ // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
+ MainWebViewActivity.pendingDialogsArrayList.add(PendingDialogDataClass(saveDialogFragment, context.getString(R.string.save_dialog)))
+ }
+ }
+ }
+ }
+ }
+
+ // Content dispositions can contain other text besides the file name, and they can be in any order.
+ // Elements are separated by semicolons. Sometimes the file names are contained in quotes.
+ @JvmStatic
+ fun getFileNameFromHeaders(context: Context, contentDispositionString: String?, contentTypeString: String?, urlString: String): String {
+ // Define a file name string.
+ var fileNameString: String
+
+ // Only process the content disposition string if it isn't null.
+ if (contentDispositionString != null) { // The content disposition is not null.
+ // Check to see if the content disposition contains a file name.
+ if (contentDispositionString.contains("filename=")) { // The content disposition contains a filename.
+ // Get the part of the content disposition after `filename=`.
+ fileNameString = contentDispositionString.substring(contentDispositionString.indexOf("filename=") + 9)
+
+ // Remove any `;` and anything after it. This removes any entries after the filename.
+ if (fileNameString.contains(";"))
+ fileNameString = fileNameString.substring(0, fileNameString.indexOf(";") - 1)
+
+ // Remove any `"` at the beginning of the string.
+ if (fileNameString.startsWith("\""))
+ fileNameString = fileNameString.substring(1)
+
+ // Remove any `"` at the end of the string.
+ if (fileNameString.endsWith("\""))
+ fileNameString = fileNameString.substring(0, fileNameString.length - 1)
+ } else { // The headers contain no useful information.
+ // Get the file name string from the URL.
+ fileNameString = getFileNameFromUrl(context, urlString, contentTypeString)
+ }
+ } else { // The content disposition is null.
+ // Get the file name string from the URL.
+ fileNameString = getFileNameFromUrl(context, urlString, contentTypeString)
+ }
+
+ // Return the file name string.
+ return fileNameString
+ }
+
+ private fun getFileNameFromUrl(context: Context, urlString: String, contentTypeString: String?): String {
+ // Convert the URL string to a URI.
+ val uri = Uri.parse(urlString)
+
+ // Get the last path segment.
+ var lastPathSegment = uri.lastPathSegment
+
+ // Use a default file name if the last path segment is null.
+ if (lastPathSegment == null) {
+ // Set the last path segment to be the generic file name.
+ lastPathSegment = context.getString(R.string.file)
+
+ // Add a file extension if it can be detected.
+ if (MimeTypeMap.getSingleton().hasMimeType(contentTypeString))
+ lastPathSegment = lastPathSegment + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(contentTypeString)
+ }
+
+ // Return the last path segment as the file name.
+ return lastPathSegment
+ }
+}
--- /dev/null
+/*
+ * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.coroutines
+
+import android.app.Activity
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.net.Uri
+import android.os.Build
+import android.provider.OpenableColumns
+import android.widget.LinearLayout
+
+import com.google.android.material.snackbar.Snackbar
+
+import com.stoutner.privacybrowser.R
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.io.ByteArrayOutputStream
+import java.lang.Exception
+
+// Declare the class constants.
+private const val SUCCESS = "Success"
+
+object SaveAboutVersionImageCoroutine {
+ fun saveImage(activity: Activity, fileUri: Uri, aboutVersionLinearLayout: LinearLayout) {
+ // Save the image using a coroutine.
+ CoroutineScope(Dispatchers.Main).launch {
+ // Create a saving image snackbar.
+ val savingImageSnackbar: Snackbar
+
+ // Process the image on the IO thread.
+ withContext(Dispatchers.IO) {
+ // Instantiate a file name string.
+ val fileNameString: String
+
+ // Query the exact file name if the API >= 26.
+ if (Build.VERSION.SDK_INT >= 26) { // The API >= 26.
+ // Get a cursor from the content resolver.
+ val contentResolverCursor = activity.contentResolver.query(fileUri, null, null, null)
+
+ // Get the file display name if the content resolve cursor is not null.
+ if (contentResolverCursor != null) { // The content resolve cursor is not null.
+ // Move to the first row.
+ contentResolverCursor.moveToFirst()
+
+ // Get the file name from the cursor.
+ fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
+
+ // Close the cursor.
+ contentResolverCursor.close()
+ } else { // The content resolve cursor is null.
+ // Use the URI last path segment as the file name string.
+ fileNameString = fileUri.lastPathSegment.toString()
+ }
+ } else { // The API is < 26.
+ // Use the URI last path segment as the file name string.
+ fileNameString = fileUri.lastPathSegment.toString()
+ }
+
+ // Use the main thread to display a snackbar.
+ withContext(Dispatchers.Main) {
+ // Create a saving image snackbar.
+ savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image, fileNameString), Snackbar.LENGTH_INDEFINITE)
+
+ // Display the saving image snackbar.
+ savingImageSnackbar.show()
+ }
+
+ // Create the about version bitmap. This can be replaced by PixelCopy once the minimum API >= 26.
+ // Once the Minimum API >= 26 Bitmap.Config.RBGA_F16 can be used instead of ARGB_8888. The linear layout commands must be run on the UI thread.
+ val aboutVersionBitmap = Bitmap.createBitmap(aboutVersionLinearLayout.width, aboutVersionLinearLayout.height, Bitmap.Config.ARGB_8888)
+
+ // Create a canvas.
+ val aboutVersionCanvas = Canvas(aboutVersionBitmap)
+
+ // Use the main thread to interact with the linear layout.
+ withContext(Dispatchers.Main) {
+ // Draw the current about version onto the bitmap.
+ aboutVersionLinearLayout.draw(aboutVersionCanvas)
+ }
+
+ // Create an about version PNG byte array output stream.
+ val aboutVersionByteArrayOutputStream = ByteArrayOutputStream()
+
+ // Convert the bitmap to a PNG. `0` is for lossless compression (the only option for a PNG). This compression takes a long time.
+ // Once the minimum API >= 30 this could be replaced with WEBP_LOSSLESS.
+ aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream)
+
+ // Create a file creation disposition string.
+ var fileCreationDisposition = SUCCESS
+
+ // Write the image inside a try block to capture any write exceptions.
+ try {
+ // Open an output stream.
+ val outputStream = activity.contentResolver.openOutputStream(fileUri)!!
+
+ // Write the webpage image to the image file.
+ aboutVersionByteArrayOutputStream.writeTo(outputStream)
+
+ // Close the output stream.
+ outputStream.close()
+ } catch (exception: Exception) {
+ // Store the error in the file creation disposition string.
+ fileCreationDisposition = exception.toString()
+ }
+
+ // Use the main thread to update the snackbars.
+ withContext(Dispatchers.Main) {
+ // Dismiss the saving image snackbar.
+ savingImageSnackbar.dismiss()
+
+ // Display a file creation disposition snackbar.
+ if (fileCreationDisposition == SUCCESS) {
+ // Create a file saved snackbar.
+ Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
+ } else {
+ Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file, fileNameString, fileCreationDisposition), Snackbar.LENGTH_INDEFINITE).show()
+ }
+ }
+ }
+ }
+ }
+}
+++ /dev/null
-/*
- * Copyright © 2016-2017,2021-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dataclasses
-
-import android.graphics.Bitmap
-
-// Define the history data class. The `@JvmField` notation can be remove once all the code has migrated to Kotlin.
-data class History(@JvmField val favoriteIcon: Bitmap, @JvmField val url: String)
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2016-2017,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dataclasses
+
+import android.graphics.Bitmap
+
+// Define the history data class. The `@JvmField` notation can be remove once all the code has migrated to Kotlin.
+data class HistoryDataClass(@JvmField val favoriteIcon: Bitmap, @JvmField val url: String)
+++ /dev/null
-/*
- * Copyright © 2021-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dataclasses
-
-import androidx.fragment.app.DialogFragment
-
-// Define the pending dialogs data class. The `@JvmField` notation can be remove once all the code has migrated to Kotlin.
-data class PendingDialog (@JvmField val dialogFragment: DialogFragment, @JvmField val tag: String)
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2021-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dataclasses
+
+import androidx.fragment.app.DialogFragment
+
+// Define the pending dialogs data class. The `@JvmField` notation can be remove once all the code has migrated to Kotlin.
+data class PendingDialogDataClass (@JvmField val dialogFragment: DialogFragment, @JvmField val tag: String)
/*
- * Copyright © 2019-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2019-2022 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
*
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
-import android.os.AsyncTask
import android.os.Bundle
import android.text.Editable
import android.text.InputType
import androidx.preference.PreferenceManager
import com.stoutner.privacybrowser.R
-import com.stoutner.privacybrowser.asynctasks.GetUrlSize
+import com.stoutner.privacybrowser.helpers.GetUrlSizeHelper
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.net.URL
// Define the class constants.
private const val URL_STRING = "url_string"
// Declare the class variables.
private lateinit var saveListener: SaveListener
- // Define the class variables.
- private var getUrlSize: AsyncTask<*, *, *>? = null
-
// The public interface is used to send information back to the parent activity.
interface SaveListener {
fun onSaveUrl(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment)
}
override fun 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.
val urlToSave = urlEditText.text.toString()
- // Wipe the file size text view.
- fileSizeTextView.text = ""
-
- // Get the file size for the current URL.
- getUrlSize = GetUrlSize(context, alertDialog, userAgentString, cookiesEnabled).execute(urlToSave)
-
// Enable the save button if the URL is populated.
saveButton.isEnabled = urlToSave.isNotEmpty()
+
+ CoroutineScope(Dispatchers.Main).launch {
+ // Create a URL size string.
+ var urlSize: String
+
+ // Get the URL size on the IO thread.
+ withContext(Dispatchers.IO) {
+ // Get the URL size.
+ urlSize = GetUrlSizeHelper.getUrl(requireContext(), URL(urlToSave), userAgentString, cookiesEnabled)
+ }
+
+ // Display the updated URL.
+ fileSizeTextView.text = urlSize
+ }
}
})
// Return the alert dialog.
return alertDialog
}
-}
\ No newline at end of file
+}
/*
- * Copyright © 2016-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2016-2022 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
*
import com.stoutner.privacybrowser.R
import com.stoutner.privacybrowser.activities.MainWebViewActivity
import com.stoutner.privacybrowser.adapters.HistoryArrayAdapter
-import com.stoutner.privacybrowser.dataclasses.History
+import com.stoutner.privacybrowser.dataclasses.HistoryDataClass
import com.stoutner.privacybrowser.views.NestedScrollWebView
// Define the class constants.
val defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.bitmap
// Create a history array list.
- val historyArrayList = ArrayList<History>()
+ val historyDataClassArrayList = ArrayList<HistoryDataClass>()
// Populate the history array list, descending from the end of the list so that the newest entries are at the top. `-1` is needed because the history array list is zero-based.
for (i in webBackForwardList.size - 1 downTo 0) {
}
// Store the favorite icon and the URL in history entry.
- val historyEntry = History(favoriteIconBitmap!!, webBackForwardList.getItemAtIndex(i).url)
+ val historyDataClassEntry = HistoryDataClass(favoriteIconBitmap!!, webBackForwardList.getItemAtIndex(i).url)
// Add this history entry to the history array list.
- historyArrayList.add(historyEntry)
+ historyDataClassArrayList.add(historyDataClassEntry)
}
// Subtract the original current page ID from the array size because the order of the array is reversed so that the newest entries are at the top. `-1` is needed because the array is zero-based.
alertDialog.show()
// Instantiate a history array adapter.
- val historyArrayAdapter = HistoryArrayAdapter(requireContext(), historyArrayList, currentPageId)
+ val historyArrayAdapter = HistoryArrayAdapter(requireContext(), historyDataClassArrayList, currentPageId)
// Get a handle for the list view.
val listView = alertDialog.findViewById<ListView>(R.id.history_listview)!!
// Return the alert dialog.
return alertDialog
}
-}
\ No newline at end of file
+}
import com.stoutner.privacybrowser.BuildConfig
import com.stoutner.privacybrowser.R
-import com.stoutner.privacybrowser.asynctasks.SaveAboutVersionImage
+import com.stoutner.privacybrowser.coroutines.SaveAboutVersionImageCoroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
// Define the save about version image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri: Uri? ->
- // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
- if (fileUri != null) {
- // Save the about version image.
- SaveAboutVersionImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout)).execute()
- }
+ // Save the file if the URI is not null, which happens if the user exits the file picker by pressing back.
+ if (fileUri != null)
+ SaveAboutVersionImageCoroutine.saveImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout))
}
override fun onCreate(savedInstanceState: Bundle?) {
/*
- * Copyright © 2018-2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2018-2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
*
package com.stoutner.privacybrowser.helpers
-import android.app.Activity
-
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
-import com.stoutner.privacybrowser.R
import com.stoutner.privacybrowser.activities.MainWebViewActivity
-import com.stoutner.privacybrowser.dataclasses.PendingDialog
+import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass
import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog.Companion.displayDialog
import com.stoutner.privacybrowser.views.NestedScrollWebView
object CheckPinnedMismatchHelper {
@JvmStatic
- fun checkPinnedMismatch(activity: Activity, fragmentManager: FragmentManager, nestedScrollWebView: NestedScrollWebView) {
+ fun checkPinnedMismatch(nestedScrollWebView: NestedScrollWebView, supportFragmentManager: FragmentManager, pinnedMismatchString: String) {
// Initialize the current SSL certificate variables.
var currentWebsiteIssuedToCName = ""
var currentWebsiteIssuedToOName = ""
// Try to show the dialog. Sometimes the window is not active.
try {
// Show the pinned mismatch alert dialog.
- pinnedMismatchDialogFragment.show(fragmentManager, activity.getString(R.string.pinned_mismatch))
+ pinnedMismatchDialogFragment.show(supportFragmentManager, pinnedMismatchString)
} catch (exception: Exception) {
// Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
- MainWebViewActivity.pendingDialogsArrayList.add(PendingDialog(pinnedMismatchDialogFragment, activity.getString(R.string.pinned_mismatch)))
+ MainWebViewActivity.pendingDialogsArrayList.add(PendingDialogDataClass(pinnedMismatchDialogFragment, pinnedMismatchString))
}
}
}
-}
\ No newline at end of file
+}
--- /dev/null
+/*
+ * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.helpers
+
+import android.content.Context
+import android.webkit.CookieManager
+
+import com.stoutner.privacybrowser.R
+
+import java.lang.Exception
+import java.net.HttpURLConnection
+import java.net.URL
+import java.text.NumberFormat
+
+object GetUrlSizeHelper {
+ fun getUrl(context: Context, url: URL, userAgent: String, cookiesEnabled: Boolean): String {
+ // Initialize the formatted file size string.
+ var formattedFileSize = context.getString(R.string.unknown_size)
+
+ // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
+ try {
+ // Instantiate the proxy helper.
+ val proxyHelper = ProxyHelper()
+
+ // Get the current proxy.
+ val proxy = proxyHelper.getCurrentProxy(context)
+
+ // Open a connection to the URL. No data is actually sent at this point.
+ val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
+
+ // Add the user agent to the header property.
+ httpUrlConnection.setRequestProperty("User-Agent", userAgent)
+
+ // Add the cookies if they are enabled.
+ if (cookiesEnabled) {
+ // Get the cookies for the current domain.
+ val 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 {
+ // Get the status code. This initiates a network connection.
+ val responseCode = httpUrlConnection.responseCode
+
+ // 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.invalid_url)
+ } else { // The response code is not an error message.
+ // Get the content length header.
+ val contentLengthString = httpUrlConnection.getHeaderField("Content-Length")
+
+ // Only process the content length string if it isn't null.
+ if (contentLengthString != null) {
+ // Convert the content length string to a long.
+ val fileSize = contentLengthString.toLong()
+
+ // Format the file size.
+ formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
+ }
+ }
+ } finally {
+ // Disconnect the HTTP URL connection.
+ httpUrlConnection.disconnect()
+ }
+ } catch (exception: Exception) {
+ // Set the formatted file size to indicate a bad URL.
+ formattedFileSize = context.getString(R.string.invalid_url)
+ }
+
+ // Return the formatted file size.
+ return formattedFileSize
+ }
+}
<string name="unknown_size">Unbekannte Größe</string>
<string name="invalid_url">Ungültige URL</string>
<string name="saving_file">Speichere Datei:</string>
- <string name="processing_image">Bild wird bearbeitet… :</string>
+ <string name="processing_image">Bild wird bearbeitet: \u0020 %1$s</string>
<string name="error_saving_file">Fehler beim Speichern der Datei %1$s: \u0020 %2$s</string>
<string name="unknown_error">Unbekannter Fehler</string>
<string name="saved_ip_addresses">Gespeicherte IP-Adressen</string>
<string name="current_ip_addresses">aktuelle IP-Adressen</string>
- <!-- Import/Export. The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+ <!-- Import/Export. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+ The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="encryption">Verschlüsselung</string>
<string-array name="encryption_type">
<item>keine</item>
<string name="import_button">importieren</string> <!-- `import` is a reserved word and cannot be used as the name. -->
<string name="decrypt">entschlüsseln</string>
<string name="export_successful">Export erfolgreich.</string>
- <string name="export_failed">Export fehlgeschlagen: %1$s</string>
- <string name="import_failed">Import fehlgeschlagen: %1$s</string>
+ <string name="export_failed">Export fehlgeschlagen: \u0020 %1$s</string>
+ <string name="import_failed">Import fehlgeschlagen: \u0020 %1$s</string>
<!-- Logcat. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="unknown_size">Tamaño desconocido</string>
<string name="invalid_url">URL inválida</string>
<string name="saving_file">Guardando archivo:</string>
- <string name="processing_image">Procesando imagen… :</string>
+ <string name="processing_image">Procesando imagen: \u0020 %1$s</string>
<string name="error_saving_file">Error al guardar %1$s: \u0020 %2$s</string>
<string name="unknown_error">Error desconocido</string>
<string name="saved_ip_addresses">Direcciones IP guardadas</string>
<string name="current_ip_addresses">Direcciones IP actuales</string>
- <!-- Import/Export. The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+ <!-- Import/Export. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+ The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="encryption">Cifrado</string>
<string-array name="encryption_type">
<item>Ninguno</item>
<string name="import_button">Importar</string> <!-- `import` is a reserved word and cannot be used as the name. -->
<string name="decrypt">Descifrar</string>
<string name="export_successful">Exportación exitosa.</string>
- <string name="export_failed">Exportación fallida: %1$s</string>
- <string name="import_failed">Importación fallida: %1$s</string>
+ <string name="export_failed">Exportación fallida: \u0020 %1$s</string>
+ <string name="import_failed">Importación fallida: \u0020 %1$s</string>
<!-- Logcat. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="unknown_size">taille inconnue</string>
<string name="invalid_url">URL invalide</string>
<string name="saving_file">Enregistrement du fichier :</string>
- <string name="processing_image">Traitement de l\'image… :</string>
+ <string name="processing_image">Traitement de l\'image: \u0020 %1$s</string>
<string name="error_saving_file">Erreur lors de l\'enregistrement de %1$s: \u0020 %2$s</string>
<string name="unknown_error">Erreur inconnue</string>
<string name="saved_ip_addresses">Adresse(s) IP sauvegardée(s)</string>
<string name="current_ip_addresses">Adresse(s) IP courante(s)</string>
- <!-- Import/Export. The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+ <!-- Import/Export. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+ The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="encryption">Chiffrement</string>
<string-array name="encryption_type">
<item>Aucun</item>
<string name="unknown_size">Dimensione sconosciuta</string>
<string name="invalid_url">URL non valida</string>
<string name="saving_file">Salvataggio file:</string>
- <string name="processing_image">Creazione immagine… :</string>
+ <string name="processing_image">Creazione immagine: \u0020 %1$s</string>
<string name="error_saving_file">Error di salvataggio di %1$s: \u0020 %2$s</string>
<string name="unknown_error">Errore sconosciuto</string>
<string name="saved_ip_addresses">Indirizzi IP salvati</string>
<string name="current_ip_addresses">Indirizzi IP attuali</string>
- <!-- Import/Export. The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+ <!-- Import/Export. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+ The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="encryption">Cifratura</string>
<string-array name="encryption_type">
<item>Nessuna</item>
<string name="import_button">Importa</string> <!-- `import` is a reserved word and cannot be used as the name. -->
<string name="decrypt">Decripta</string>
<string name="export_successful">Esportazione riuscita</string>
- <string name="export_failed">Esportazione fallita: %1$s</string>
- <string name="import_failed">Importazione fallita: %1$s</string>
+ <string name="export_failed">Esportazione fallita: \u0020 %1$s</string>
+ <string name="import_failed">Importazione fallita: \u0020 %1$s</string>
<!-- Logcat. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="unknown_size">tamanho desconhecido</string>
<string name="invalid_url">URL inválida</string>
<string name="saving_file">Salvando file:</string>
- <string name="processing_image">Processando imagem… :</string>
+ <string name="processing_image">Processando imagem: \u0020 %1$s</string>
<string name="error_saving_file">Erro ao salvar %1$s: \u0020 %2$s</string>
<string name="unknown_error">Erro desconhecido</string>
<string name="saved_ip_addresses">Endereços IP salvos</string>
<string name="current_ip_addresses">Endereços IP atuais</string>
- <!-- Import/Export. The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+ <!-- Import/Export. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+ The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="encryption">Encriptação</string>
<string-array name="encryption_type">
<item>Nenhum</item>
<string name="import_button">Importar</string> <!-- `import` is a reserved word and cannot be used as the name. -->
<string name="decrypt">Descriptografar</string>
<string name="export_successful">Exportação bem sucedida.</string>
- <string name="export_failed">A exportação falhou: %1$s</string>
- <string name="import_failed">A importação falhou: %1$s</string>
+ <string name="export_failed">A exportação falhou: \u0020 %1$s</string>
+ <string name="import_failed">A importação falhou: \u0020 %1$s</string>
<!-- Logcat. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="unknown_size">неизвестный размер</string>
<string name="invalid_url">неправильный URL</string>
<string name="saving_file">Сохранение файла:</string>
- <string name="processing_image">Обработка изображения… :</string>
+ <string name="processing_image">Обработка изображения: \u0020 %1$s</string>
<string name="error_saving_file">Ошибка сохранения %1$s: \u0020 %2$s</string>
<string name="unknown_error">Неизвестная ошибка</string>
<string name="saved_ip_addresses">Сохраненные IP-адреса</string>
<string name="current_ip_addresses">Текущие IP-адреса</string>
- <!-- Import/Export. The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+ <!-- Import/Export. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+ The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="encryption">Шифрование</string>
<string-array name="encryption_type">
<item>Нет</item>
<string name="import_button">Импорт</string> <!-- `import` is a reserved word and cannot be used as the name. -->
<string name="decrypt">Расшифровать</string>
<string name="export_successful">Экспорт выполнен.</string>
- <string name="export_failed">Сбой при экспорте: %1$s</string>
- <string name="import_failed">Сбой при импорте: %1$s</string>
+ <string name="export_failed">Сбой при экспорте: \u0020 %1$s</string>
+ <string name="import_failed">Сбой при импорте: \u0020 %1$s</string>
<!-- Logcat. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="saved_ip_addresses">Kayıtlı IP adresleri</string>
<string name="current_ip_addresses">Geçerli IP adresleri</string>
- <!-- Import/Export. The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+ <!-- Import/Export. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+ The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="encryption">Şifreleme</string>
<string-array name="encryption_type">
<item>Hiçbiri</item>
<string name="import_button">İçeri aktar</string> <!-- `import` is a reserved word and cannot be used as the name. -->
<string name="decrypt">Şifre çöz (Decrypt)</string>
<string name="export_successful">Dışa aktarım başarılı</string>
- <string name="export_failed">Dışa aktarım başarısız: %1$s</string>
- <string name="import_failed">İçe aktarım başarısız: %1$s</string>
+ <string name="export_failed">Dışa aktarım başarısız: \u0020 %1$s</string>
+ <string name="import_failed">İçe aktarım başarısız: \u0020 %1$s</string>
<!-- Logcat. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="invalid_url">invalid URL</string>
<string name="saving_file">Saving file:</string>
<string name="saved">%1$s saved.</string>
- <string name="processing_image">Processing image… :</string>
+ <string name="processing_image">Processing image: \u0020 %1$s</string>
<string name="error_saving_file">Error saving %1$s: \u0020 %2$s</string>
<string name="unknown_error">Unknown error</string>
<string name="saved_ip_addresses">Saved IP addresses</string>
<string name="current_ip_addresses">Current IP addresses</string>
- <!-- Import/Export. The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+ <!-- Import/Export. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+ The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="encryption">Encryption</string>
<string-array name="encryption_type">
<item>None</item>
<string name="privacy_browser_settings_pbs">Privacy Browser Settings %1$s.pbs</string>
<string name="privacy_browser_settings_pbs_aes">Privacy Browser Settings %1$s.pbs.aes</string>
<string name="export_successful">Export successful.</string>
- <string name="export_failed">Export failed: %1$s</string>
- <string name="import_failed">Import failed: %1$s</string>
+ <string name="export_failed">Export failed: \u0020 %1$s</string>
+ <string name="import_failed">Import failed: \u0020 %1$s</string>
<!-- Logcat. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
The `%1$s` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->