/**
* Copyright 2015-2017 Soren Stoutner <soren@stoutner.com>.
*
+ * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+ *
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
* Privacy Browser is free software: you can redistribute it and/or modify
import android.annotation.SuppressLint;
import android.app.DialogFragment;
import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
// `sslCertificate` is public static so it can be accessed from `ViewSslCertificate`. It is also used in `onCreate()`.
public static SslCertificate sslCertificate;
+ // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`.
+ public static String orbotStatus;
+
// `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
private DrawerLayout drawerLayout;
// `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
private SwipeRefreshLayout swipeRefreshLayout;
- // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, and `onRestart()`.
+ // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
private CookieManager cookieManager;
// `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrlFromTextBox()`.
// It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`.
private Boolean javaScriptEnabled;
- // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
+ // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applySettings()`.
private boolean firstPartyCookiesEnabled;
// `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
// `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applySettings()`.
private boolean translucentNavigationBarOnFullscreen;
+ // `proxyThroughOrbot` is used in `onCreate()` and `applySettings()`
+ private boolean proxyThroughOrbot;
+
+ // `pendingUrl` is used in `onCreate()` and `applySettings()`
+ private String pendingUrl;
+
// `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
private LinearLayout findOnPageLinearLayout;
// Update the URL in urlTextBox when the page starts to load.
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
- // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
- formattedUrlString = url;
+ // Check to see if we are waiting on Orbot.
+ if (pendingUrl.isEmpty()) { // We are not waiting on Orbot, so we need to process the URL.
+ // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
+ formattedUrlString = url;
- // Display the loading URL is the URL text box.
- urlTextBox.setText(url);
+ // Display the loading URL is the URL text box.
+ urlTextBox.setText(url);
+ }
}
// Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load.
@Override
public void onPageFinished(WebView view, String url) {
- formattedUrlString = url;
+ // Check to see if we are waiting on Orbot.
+ if (pendingUrl.isEmpty()) { // we are not waiting on Orbot, so we need to process the URL.
+ formattedUrlString = url;
- // Only update urlTextBox if the user is not typing in it.
- if (!urlTextBox.hasFocus()) {
- urlTextBox.setText(formattedUrlString);
- }
+ // Only update urlTextBox if the user is not typing in it.
+ if (!urlTextBox.hasFocus()) {
+ urlTextBox.setText(formattedUrlString);
+ }
- // Store the SSL certificate so it can be accessed from `ViewSslCertificate`.
- sslCertificate = mainWebView.getCertificate();
+ // Store the SSL certificate so it can be accessed from `ViewSslCertificate`.
+ sslCertificate = mainWebView.getCertificate();
+ }
}
// Handle SSL Certificate errors.
// Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
customHeaders.put("X-Requested-With", "");
+ // Set the initial Orbot status.
+ orbotStatus = "unknown";
+
+ // Create a Orbot status `BroadcastReceiver`.
+ BroadcastReceiver orbotStatusBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Store the content of the status message in `orbotStatus`.
+ orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
+
+ // If we are waiting on `pendingUrl`, load it now that Orbot is connected.
+ if (orbotStatus.equals("ON") && !pendingUrl.isEmpty()) {
+
+ // Wait 500 milliseconds, because Orbot isn't really ready yet.
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException exception) {
+ // Do nothing.
+ }
+
+ // Load `pendingUrl`.
+ formattedUrlString = pendingUrl;
+ mainWebView.loadUrl(formattedUrlString, customHeaders);
+
+ // Reset `pendingUrl` to be empty.
+ pendingUrl = "";
+ }
+ }
+ };
+
+ // Register `orbotStatusBroadcastReceiver` on `this` context.
+ this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
+
// Initialize the default preference values the first time the program is run. `this` is the context. `false` keeps this command from resetting any current preferences back to default.
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
formattedUrlString = homepage;
}
- // Load the initial website.
- mainWebView.loadUrl(formattedUrlString, customHeaders);
+ // Initialize `pendingUrl`.
+ pendingUrl = "";
+
+ if (proxyThroughOrbot & !orbotStatus.equals("ON")) { // We are waiting on Orbot.
+ // Save `formattedUrlString` in `pendingUrl`.
+ pendingUrl = formattedUrlString;
+
+ // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
+ mainWebView.loadData("<html><body><br/><center><h1>Waiting for Orbot to connect...</h1></center></body></html>", "text/html", null);
+ } else {
+ // Load the initial website.
+ mainWebView.loadUrl(formattedUrlString, customHeaders);
+ }
// If the favorite icon is null, load the default.
if (favoriteIcon == null) {
startActivity(settingsIntent);
break;
+ /*
+ case R.id.domains:
+ // Launch `DomainsList`.
+ Intent domainsIntent = new Intent(this, DomainsList.class);
+ startActivity(domainsIntent);
+ break;
+ */
+
case R.id.guide:
// Launch `Guide`.
Intent guideIntent = new Intent(this, Guide.class);
// Destroy the internal state of `mainWebView`.
mainWebView.destroy();
+ // Manually delete the `app_webview` folder, which contains an additional `WebView` cache. See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
+ Runtime runtime = Runtime.getRuntime();
+ String dataDirString = getApplicationInfo().dataDir; // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+ try {
+ runtime.exec("rm -rf " + dataDirString + "/app_webview");
+ } catch (IOException e) {
+ // Do nothing if the files do not exist.
+ }
+
// Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
if (Build.VERSION.SDK_INT >= 21) {
finishAndRemoveTask();
@Override
public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
- // Get a handle for the system `DOWNLOAD_SERVICE`.
- DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+ // Download the image if it has an HTTP or HTTPS URI.
+ if (imageUrl.startsWith("http")) {
+ // Get a handle for the system `DOWNLOAD_SERVICE`.
+ DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
- // Parse `imageUrl`.
- DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
+ // Parse `imageUrl`.
+ DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
- // Get the file name from `dialogFragment`.
- EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name);
- String imageName = downloadImageNameEditText.getText().toString();
+ // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
+ if (firstPartyCookiesEnabled) {
+ // Get the cookies for `imageUrl`.
+ String cookies = cookieManager.getCookie(imageUrl);
- // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
- if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`.
- downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName);
- } else { // Only set the title using `imageName`.
- downloadRequest.setTitle(imageName);
- }
+ // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
+ downloadRequest.addRequestHeader("Cookie", cookies);
+ }
+
+ // Get the file name from `dialogFragment`.
+ EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name);
+ String imageName = downloadImageNameEditText.getText().toString();
+
+ // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
+ if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`.
+ downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName);
+ } else { // Only set the title using `imageName`.
+ downloadRequest.setTitle(imageName);
+ }
- // Allow `MediaScanner` to index the download if it is a media file.
- downloadRequest.allowScanningByMediaScanner();
+ // Allow `MediaScanner` to index the download if it is a media file.
+ downloadRequest.allowScanningByMediaScanner();
- // Add the URL as the description for the download.
- downloadRequest.setDescription(imageUrl);
+ // Add the URL as the description for the download.
+ downloadRequest.setDescription(imageUrl);
- // Show the download notification after the download is completed.
- downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ // Show the download notification after the download is completed.
+ downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- // Initiate the download.
- downloadManager.enqueue(downloadRequest);
+ // Initiate the download.
+ downloadManager.enqueue(downloadRequest);
+ } else { // The image is not an HTTP or HTTPS URI.
+ Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
+ }
}
@Override
public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
- // Get a handle for the system `DOWNLOAD_SERVICE`.
- DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+ // Download the file if it has an HTTP or HTTPS URI.
+ if (downloadUrl.startsWith("http")) {
- // Parse `downloadUrl`.
- DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
+ // Get a handle for the system `DOWNLOAD_SERVICE`.
+ DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
- // Get the file name from `dialogFragment`.
- EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
- String fileName = downloadFileNameEditText.getText().toString();
+ // Parse `downloadUrl`.
+ DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
- // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
- if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download location to `/sdcard/Android/data/com.stoutner.privacybrowser.standard/files` named `fileName`.
- downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
- } else { // Only set the title using `fileName`.
- downloadRequest.setTitle(fileName);
- }
+ // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
+ if (firstPartyCookiesEnabled) {
+ // Get the cookies for `downloadUrl`.
+ String cookies = cookieManager.getCookie(downloadUrl);
- // Allow `MediaScanner` to index the download if it is a media file.
- downloadRequest.allowScanningByMediaScanner();
+ // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
+ downloadRequest.addRequestHeader("Cookie", cookies);
+ }
- // Add the URL as the description for the download.
- downloadRequest.setDescription(downloadUrl);
+ // Get the file name from `dialogFragment`.
+ EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
+ String fileName = downloadFileNameEditText.getText().toString();
+
+ // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
+ if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download location to `/sdcard/Android/data/com.stoutner.privacybrowser.standard/files` named `fileName`.
+ downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
+ } else { // Only set the title using `fileName`.
+ downloadRequest.setTitle(fileName);
+ }
- // Show the download notification after the download is completed.
- downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ // Allow `MediaScanner` to index the download if it is a media file.
+ downloadRequest.allowScanningByMediaScanner();
- // Initiate the download.
- downloadManager.enqueue(downloadRequest);
+ // Add the URL as the description for the download.
+ downloadRequest.setDescription(downloadUrl);
+
+ // Show the download notification after the download is completed.
+ downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+
+ // Initiate the download.
+ downloadManager.enqueue(downloadRequest);
+ } else { // The download is not an HTTP or HTTPS URI.
+ Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
+ }
}
public void viewSslCertificate(View view) {
String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
- String javaScriptDisabledCustomSearchString = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
+ String javaScriptDisabledSearchCustomURLString = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
- String javaScriptEnabledCustomSearchString = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
+ String javaScriptEnabledSearchCustomURLString = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
+ String torHomepageString = sharedPreferences.getString("tor_homepage", "https://3g2upl4pq6kufc4m.onion");
+ String torJavaScriptDisabledSearchString = sharedPreferences.getString("tor_javascript_disabled_search", "https://3g2upl4pq6kufc4m.onion/html/?q=");
+ String torJavaScriptDisabledSearchCustomURLString = sharedPreferences.getString("tor_javascript_disabled_search_custom_url", "");
+ String torJavaScriptEnabledSearchString = sharedPreferences.getString("tor_javascript_enabled_search", "https://3g2upl4pq6kufc4m.onion/?q=");
+ String torJavaScriptEnabledSearchCustomURLString = sharedPreferences.getString("tor_javascript_enabled_search_custom_url", "");
String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false);
adBlockerEnabled = sharedPreferences.getBoolean("block_ads", true);
boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
- boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
+ proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("enable_full_screen_browsing_mode", false);
hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
}
}
- // Apply the other settings from `sharedPreferences`.
- homepage = homepageString;
+ // Set the homepage, search, and proxy options.
+ if (proxyThroughOrbot) { // Set the Tor options.
+ // Set `torHomepageString` as `homepage`.
+ homepage = torHomepageString;
+
+ // Set JavaScript disabled search.
+ if (torJavaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string.
+ javaScriptDisabledSearchURL = torJavaScriptDisabledSearchCustomURLString;
+ } else { // Use the string from the pre-built list.
+ javaScriptDisabledSearchURL = torJavaScriptDisabledSearchString;
+ }
+
+ // Set JavaScript enabled search.
+ if (torJavaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string.
+ javaScriptEnabledSearchURL = torJavaScriptEnabledSearchCustomURLString;
+ } else { // Use the string from the pre-built list.
+ javaScriptEnabledSearchURL = torJavaScriptEnabledSearchString;
+ }
+
+ // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
+ OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
+ } else { // Set the non-Tor options.
+ // Set `homepageString` as `homepage`.
+ homepage = homepageString;
+
+ // Set JavaScript disabled search.
+ if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string.
+ javaScriptDisabledSearchURL = javaScriptDisabledSearchCustomURLString;
+ } else { // Use the string from the pre-built list.
+ javaScriptDisabledSearchURL = javaScriptDisabledSearchString;
+ }
+
+ // Set JavaScript enabled search.
+ if (javaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string.
+ javaScriptEnabledSearchURL = javaScriptEnabledSearchCustomURLString;
+ } else { // Use the string from the pre-built list.
+ javaScriptEnabledSearchURL = javaScriptEnabledSearchString;
+ }
+
+ // Reset the proxy to default. The host is `""` and the port is `"0"`.
+ OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
+ }
+
+ // Set swipe to refresh.
swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
// Set the user agent initial status.
break;
}
- // Set JavaScript disabled search.
- if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string.
- javaScriptDisabledSearchURL = javaScriptDisabledCustomSearchString;
- } else { // Use the string from the pre-built list.
- javaScriptDisabledSearchURL = javaScriptDisabledSearchString;
- }
-
- // Set JavaScript enabled search.
- if (javaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string.
- javaScriptEnabledSearchURL = javaScriptEnabledCustomSearchString;
- } else { // Use the string from the pre-built list.
- javaScriptEnabledSearchURL = javaScriptEnabledSearchString;
- }
-
// Set Do Not Track status.
if (doNotTrackEnabled) {
customHeaders.put("DNT", "1");
customHeaders.remove("DNT");
}
- // Set Orbot proxy status.
- if (proxyThroughOrbot) {
- // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
- OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
- } else { // Reset the proxy to default. The host is `""` and the port is `"0"`.
- OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
- }
-
// If we are in full screen mode update the `SYSTEM_UI` flags.
if (inFullScreenBrowsingMode) {
if (hideSystemBarsOnFullscreen) { // Hide everything.
}
}
}
+
+ if (proxyThroughOrbot & !orbotStatus.equals("ON")) { // We are waiting on Orbot.
+ // Save `formattedUrlString` in `pendingUrl`.
+ pendingUrl = formattedUrlString;
+
+ // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
+ mainWebView.loadData("<html><body><br/><center><h1>Waiting for Orbot to connect...</h1></center></body></html>", "text/html", null);
+ }
}
private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {