/**
- * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
+ * 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>.
*
import android.webkit.SslErrorHandler;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
+import android.webkit.WebResourceResponse;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.stoutner.privacybrowser.dialogs.UrlHistory;
import com.stoutner.privacybrowser.dialogs.ViewSslCertificate;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
// We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
public class MainWebView extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener,
public static SslCertificate sslCertificate;
-
// `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()`.
// `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
private String javaScriptEnabledSearchURL;
+ // `adBlockerEnabled` is used in `onCreate()` and `applySettings()`.
+ private boolean adBlockerEnabled;
+
// `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applySettings()`.
private boolean fullScreenBrowsingModeEnabled;
// `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
private InputMethodManager inputMethodManager;
+ // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
+ private RelativeLayout mainWebViewRelativeLayout;
+
@Override
// Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers.
@SuppressLint("SetJavaScriptEnabled")
// Get handles for views that need to be accessed.
drawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout);
rootCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.root_coordinatorlayout);
+ mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.main_webview_relativelayout);
mainWebView = (WebView) findViewById(R.id.mainWebView);
findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext);
// Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
- /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
- */
+ /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
+ */
rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
// Set `rootCoordinatorLayout` to fill the whole screen.
// drawerToggle creates the hamburger icon at the start of the AppBar.
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
+ // Initialize `adServerSet`.
+ final Set<String> adServersSet = new HashSet<>();
+
+ // Load the list of ad servers into memory.
+ try {
+ // Load `pgl.yoyo.org_adservers.txt` into a `BufferedReader`.
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("pgl.yoyo.org_adservers.txt")));
+
+ // Create a string for storing each ad server.
+ String adServer;
+
+ // Populate `adServersSet`.
+ while ((adServer = bufferedReader.readLine()) != null) {
+ adServersSet.add(adServer);
+ }
+
+ // Close `bufferedReader`.
+ bufferedReader.close();
+ } catch (IOException ioException) {
+ // We're pretty sure the asset exists, so we don't need to worry about the `IOException` ever being thrown.
+ }
+
mainWebView.setWebViewClient(new WebViewClient() {
// `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
// We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
}
}
+ // Block ads. We have to use the deprecated `shouldInterceptRequest` until minimum API >= 21.
+ @SuppressWarnings("deprecation")
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, String url){
+ if (adBlockerEnabled) { // Block ads.
+ // Extract the host from `url`.
+ Uri requestUri = Uri.parse(url);
+ String requestHost = requestUri.getHost();
+
+ // Create a variable to track if this is an ad server.
+ boolean requestHostIsAdServer = false;
+
+ // Check all the subdomains of `requestHost` if it is not `null`.
+ if (requestHost != null) {
+ while (requestHost.contains(".")) {
+ if (adServersSet.contains(requestHost)) {
+ requestHostIsAdServer = true;
+ }
+
+ // Strip out the lowest subdomain of `requestHost`.
+ requestHost = requestHost.substring(requestHost.indexOf(".") + 1);
+ }
+ }
+
+ if (requestHostIsAdServer) { // It is an ad server.
+ // Return an empty `WebResourceResponse`.
+ return new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
+ } else { // It is not an ad server.
+ // `return null` loads the requested resource.
+ return null;
+ }
+ } else { // Ad blocking is disabled.
+ // `return null` loads the requested resource.
+ return null;
+ }
+ }
+
// Update the URL in urlTextBox when the page starts to load.
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
customHeaders.clear();
// Detach all views from `mainWebViewRelativeLayout`.
- RelativeLayout mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.mainWebViewRelativeLayout);
mainWebViewRelativeLayout.removeAllViews();
// Destroy the internal state of `mainWebView`.
@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 save in the the `DIRECTORY_DOWNLOADS` using `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);
+
+ // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
+ downloadRequest.addRequestHeader("Cookie", cookies);
+ }
- // Allow `MediaScanner` to index the download if it is a media file.
- downloadRequest.allowScanningByMediaScanner();
+ // Get the file name from `dialogFragment`.
+ EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
+ String fileName = downloadFileNameEditText.getText().toString();
- // Add the URL as the description for the download.
- downloadRequest.setDescription(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);
+ }
+
+ // Allow `MediaScanner` to index the download if it is a media file.
+ downloadRequest.allowScanningByMediaScanner();
- // Show the download notification after the download is completed.
- downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ // Add the URL as the description for the download.
+ downloadRequest.setDescription(downloadUrl);
- // Initiate the download.
- downloadManager.enqueue(downloadRequest);
+ // 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 homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false);
- boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", true);
+ adBlockerEnabled = sharedPreferences.getBoolean("block_ads", true);
+ boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("enable_full_screen_browsing_mode", false);
hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);