2 * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser;
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.app.DialogFragment;
25 import android.app.DownloadManager;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.res.Configuration;
29 import android.graphics.Bitmap;
30 import android.graphics.drawable.BitmapDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.net.http.SslError;
34 import android.os.Build;
35 import android.os.Bundle;
36 import android.preference.PreferenceManager;
37 import android.support.design.widget.NavigationView;
38 import android.support.design.widget.Snackbar;
39 import android.support.v4.content.ContextCompat;
40 import android.support.v4.view.GravityCompat;
41 import android.support.v4.widget.DrawerLayout;
42 import android.support.v4.widget.SwipeRefreshLayout;
43 import android.support.v7.app.ActionBar;
44 import android.support.v7.app.ActionBarDrawerToggle;
45 import android.support.v7.app.AppCompatActivity;
46 import android.support.v7.widget.Toolbar;
47 import android.util.Patterns;
48 import android.view.KeyEvent;
49 import android.view.Menu;
50 import android.view.MenuItem;
51 import android.view.View;
52 import android.view.inputmethod.InputMethodManager;
53 import android.webkit.CookieManager;
54 import android.webkit.DownloadListener;
55 import android.webkit.SslErrorHandler;
56 import android.webkit.WebChromeClient;
57 import android.webkit.WebStorage;
58 import android.webkit.WebView;
59 import android.webkit.WebViewClient;
60 import android.webkit.WebViewDatabase;
61 import android.widget.EditText;
62 import android.widget.FrameLayout;
63 import android.widget.ImageView;
64 import android.widget.ProgressBar;
66 import java.io.UnsupportedEncodingException;
67 import java.net.MalformedURLException;
69 import java.net.URLEncoder;
70 import java.util.HashMap;
73 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
74 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener, SslCertificateError.SslCertificateErrorListener {
75 // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
76 // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
77 public static Bitmap favoriteIcon;
79 // `mainWebView` is public static so it can be accessed from `SettingsFragment`.
80 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, and `loadUrlFromTextBox()`.
81 public static WebView mainWebView;
83 // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`.
84 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
85 public static String formattedUrlString;
87 // `mainMenu` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreateOptionsMenu()` and `onOptionsItemSelected()`.
88 public static Menu mainMenu;
90 // `cookieManager` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
91 public static CookieManager cookieManager;
93 // `javaScriptEnabled` is public static so it can be accessed from `SettingsFragment`.
94 // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
95 public static boolean javaScriptEnabled;
97 // `firstPartyCookiesEnabled` is public static so it can be accessed from `SettingsFragment`.
98 // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, and `onOptionsItemSelected()`.
99 public static boolean firstPartyCookiesEnabled;
101 // `thirdPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, and `onOptionsItemSelected()`.
102 public static boolean thirdPartyCookiesEnabled;
104 // `domStorageEnabled` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `onOptionsItemSelected()`.
105 public static boolean domStorageEnabled;
107 // `saveFormDataEnabled` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `onOptionsItemSelected()`.
108 public static boolean saveFormDataEnabled;
110 // `javaScriptDisabledSearchURL` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()` and `loadURLFromTextBox()`.
111 public static String javaScriptDisabledSearchURL;
113 // `javaScriptEnabledSearchURL` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()` and `loadURLFromTextBox()`.
114 public static String javaScriptEnabledSearchURL;
116 // `homepage` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()` and `onOptionsItemSelected()`.
117 public static String homepage;
119 // `swipeToRefresh` is public static so it can be accessed from SettingsFragment. It is also used in onCreate().
120 public static SwipeRefreshLayout swipeToRefresh;
122 // `swipeToRefreshEnabled` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()`.
123 public static boolean swipeToRefreshEnabled;
125 // `customHeader` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()` and `loadUrlFromTextBox()`.
126 public static Map<String, String> customHeaders = new HashMap<String, String>();
130 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
131 private ActionBarDrawerToggle drawerToggle;
133 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
134 private DrawerLayout drawerLayout;
136 // `privacyIcon` is used in `onCreateOptionsMenu()` and `updatePrivacyIcon()`.
137 private MenuItem privacyIcon;
139 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
140 private EditText urlTextBox;
142 // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
145 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
146 private SslErrorHandler sslErrorHandler;
149 // 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.
150 @SuppressLint("SetJavaScriptEnabled")
151 protected void onCreate(Bundle savedInstanceState) {
152 super.onCreate(savedInstanceState);
153 setContentView(R.layout.main_coordinatorlayout);
155 // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
156 Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
157 setSupportActionBar(supportAppBar);
158 final ActionBar appBar = getSupportActionBar();
160 // This is needed to get rid of the Android Studio warning that appBar might be null.
161 assert appBar != null;
163 // Add the custom url_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
164 appBar.setCustomView(R.layout.url_bar);
165 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
167 // Set the "go" button on the keyboard to load the URL in urlTextBox.
168 urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
169 urlTextBox.setOnKeyListener(new View.OnKeyListener() {
170 public boolean onKey(View v, int keyCode, KeyEvent event) {
171 // If the event is a key-down event on the "enter" button, load the URL.
172 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
173 // Load the URL into the mainWebView and consume the event.
175 loadUrlFromTextBox();
176 } catch (UnsupportedEncodingException e) {
179 // If the enter key was pressed, consume the event.
182 // If any other key was pressed, do not consume the event.
188 final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
190 // Implement swipe to refresh
191 swipeToRefresh = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
192 assert swipeToRefresh != null; //This assert removes the incorrect warning on the following line that swipeToRefresh might be null.
193 swipeToRefresh.setColorSchemeResources(R.color.blue);
194 swipeToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
196 public void onRefresh() {
197 mainWebView.reload();
201 mainWebView = (WebView) findViewById(R.id.mainWebView);
203 // Create the navigation drawer.
204 drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
205 // The DrawerTitle identifies the drawer in accessibility mode.
206 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
208 // Listen for touches on the navigation menu.
209 final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
210 assert navigationView != null; // This assert removes the incorrect warning on the following line that navigationView might be null.
211 navigationView.setNavigationItemSelectedListener(this);
213 // drawerToggle creates the hamburger icon at the start of the AppBar.
214 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
216 // 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).
217 customHeaders.put("X-Requested-With", "");
219 mainWebView.setWebViewClient(new WebViewClient() {
220 // shouldOverrideUrlLoading makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
222 public boolean shouldOverrideUrlLoading(WebView view, String url) {
223 // Use an external email program if the link begins with "mailto:".
224 if (url.startsWith("mailto:")) {
225 // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
226 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
228 // Parse the url and set it as the data for the `Intent`.
229 emailIntent.setData(Uri.parse(url));
231 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
232 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
235 startActivity(emailIntent);
237 } else { // Load the URL in Privacy Browser.
238 mainWebView.loadUrl(url, customHeaders);
243 // Update the URL in urlTextBox when the page starts to load.
245 public void onPageStarted(WebView view, String url, Bitmap favicon) {
246 urlTextBox.setText(url);
249 // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load.
251 public void onPageFinished(WebView view, String url) {
252 formattedUrlString = url;
254 // Only update urlTextBox if the user is not typing in it.
255 if (!urlTextBox.hasFocus()) {
256 urlTextBox.setText(formattedUrlString);
260 // Handle SSL Certificate errors.
262 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
263 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
264 sslErrorHandler = handler;
266 // Display the SSL error `AlertDialog`.
267 DialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error);
268 sslCertificateErrorDialogFragment.show(getFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
272 mainWebView.setWebChromeClient(new WebChromeClient() {
273 // Update the progress bar when a page is loading.
275 public void onProgressChanged(WebView view, int progress) {
276 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
277 progressBar.setProgress(progress);
278 if (progress < 100) {
279 progressBar.setVisibility(View.VISIBLE);
281 progressBar.setVisibility(View.GONE);
283 //Stop the SwipeToRefresh indicator if it is running
284 swipeToRefresh.setRefreshing(false);
288 // Set the favorite icon when it changes.
290 public void onReceivedIcon(WebView view, Bitmap icon) {
291 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
294 // Place the favorite icon in the appBar.
295 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
296 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
299 // Enter full screen video
301 public void onShowCustomView(View view, CustomViewCallback callback) {
304 // Show the fullScreenVideoFrameLayout.
305 assert fullScreenVideoFrameLayout != null; //This assert removes the incorrect warning on the following line that fullScreenVideoFrameLayout might be null.
306 fullScreenVideoFrameLayout.addView(view);
307 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
309 // Hide the mainWebView.
310 mainWebView.setVisibility(View.GONE);
312 // Hide the ad if this is the free flavor.
313 BannerAd.hideAd(adView);
315 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
316 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
317 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
319 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
322 // Exit full screen video
323 public void onHideCustomView() {
326 // Show the mainWebView.
327 mainWebView.setVisibility(View.VISIBLE);
329 // Show the ad if this is the free flavor.
330 BannerAd.showAd(adView);
332 // Hide the fullScreenVideoFrameLayout.
333 assert fullScreenVideoFrameLayout != null; //This assert removes the incorrect warning on the following line that fullScreenVideoFrameLayout might be null.
334 fullScreenVideoFrameLayout.removeAllViews();
335 fullScreenVideoFrameLayout.setVisibility(View.GONE);
339 // Allow the downloading of files.
340 mainWebView.setDownloadListener(new DownloadListener() {
341 // Launch the Android download manager when a link leads to a download.
343 public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
344 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
345 DownloadManager.Request requestUri = new DownloadManager.Request(Uri.parse(url));
347 // Add the URL as the description for the download.
348 requestUri.setDescription(url);
350 // Show the download notification after the download is completed.
351 requestUri.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
353 // Initiate the download and display a Snackbar.
354 downloadManager.enqueue(requestUri);
355 Snackbar.make(findViewById(R.id.mainWebView), R.string.download_started, Snackbar.LENGTH_SHORT).show();
359 // Allow pinch to zoom.
360 mainWebView.getSettings().setBuiltInZoomControls(true);
362 // Hide zoom controls.
363 mainWebView.getSettings().setDisplayZoomControls(false);
366 // Initialize the default preference values the first time the program is run.
367 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
369 // Get the shared preference values.
370 SharedPreferences savedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
372 // Set JavaScript initial status. The default value is false.
373 javaScriptEnabled = savedPreferences.getBoolean("javascript_enabled", false);
374 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
376 // Initialize cookieManager.
377 cookieManager = CookieManager.getInstance();
379 // Set cookies initial status. The default value is false.
380 firstPartyCookiesEnabled = savedPreferences.getBoolean("first_party_cookies_enabled", false);
381 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
383 // Set third-party cookies initial status if API >= 21. The default value is false.
384 if (Build.VERSION.SDK_INT >= 21) {
385 thirdPartyCookiesEnabled = savedPreferences.getBoolean("third_party_cookies_enabled", false);
386 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
389 // Set DOM storage initial status. The default value is false.
390 domStorageEnabled = savedPreferences.getBoolean("dom_storage_enabled", false);
391 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
393 // Set the saved form data initial status. The default is false.
394 saveFormDataEnabled = savedPreferences.getBoolean("save_form_data_enabled", false);
395 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
397 // Set the user agent initial status.
398 String userAgentString = savedPreferences.getString("user_agent", "Default user agent");
399 switch (userAgentString) {
400 case "Default user agent":
404 case "Custom user agent":
405 // Set the custom user agent on mainWebView, The default is "PrivacyBrowser/1.0".
406 mainWebView.getSettings().setUserAgentString(savedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
410 // Set the selected user agent on mainWebView. The default is "PrivacyBrowser/1.0".
411 mainWebView.getSettings().setUserAgentString(savedPreferences.getString("user_agent", "PrivacyBrowser/1.0"));
415 // Set the initial string for JavaScript disabled search.
416 if (savedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=").equals("Custom URL")) {
417 // Get the custom URL string. The default is "".
418 javaScriptDisabledSearchURL = savedPreferences.getString("javascript_disabled_search_custom_url", "");
420 // Use the string from javascript_disabled_search.
421 javaScriptDisabledSearchURL = savedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
424 // Set the initial string for JavaScript enabled search.
425 if (savedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=").equals("Custom URL")) {
426 // Get the custom URL string. The default is "".
427 javaScriptEnabledSearchURL = savedPreferences.getString("javascript_enabled_search_custom_url", "");
429 // Use the string from javascript_enabled_search.
430 javaScriptEnabledSearchURL = savedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
434 // Set the homepage initial status. The default value is `https://www.duckduckgo.com`.
435 homepage = savedPreferences.getString("homepage", "https://www.duckduckgo.com");
437 // Set the font size initial status. the default value is `100`.
438 String defaultFontSizeString = savedPreferences.getString("default_font_size", "100");
439 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
441 // Set the swipe to refresh initial status. The default is `true`.
442 swipeToRefreshEnabled = savedPreferences.getBoolean("swipe_to_refresh_enabled", true);
443 swipeToRefresh.setEnabled(swipeToRefreshEnabled);
446 // Get the intent information that started the app.
447 final Intent intent = getIntent();
449 if (intent.getData() != null) {
450 // Get the intent data and convert it to a string.
451 final Uri intentUriData = intent.getData();
452 formattedUrlString = intentUriData.toString();
455 // If formattedUrlString is null assign the homepage to it.
456 if (formattedUrlString == null) {
457 formattedUrlString = homepage;
460 // Load the initial website.
461 mainWebView.loadUrl(formattedUrlString, customHeaders);
463 // If the favorite icon is null, load the default.
464 if (favoriteIcon == null) {
465 // We have to use `ContextCompat` until API >= 21.
466 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
467 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
468 favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
471 // Initialize AdView for the free flavor and request an ad. If this is not the free flavor BannerAd.requestAd() does nothing.
472 adView = findViewById(R.id.adView);
473 BannerAd.requestAd(adView);
478 protected void onNewIntent(Intent intent) {
479 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
482 if (intent.getData() != null) {
483 // Get the intent data and convert it to a string.
484 final Uri intentUriData = intent.getData();
485 formattedUrlString = intentUriData.toString();
488 // Close the navigation drawer if it is open.
489 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
490 drawerLayout.closeDrawer(GravityCompat.START);
494 mainWebView.loadUrl(formattedUrlString, customHeaders);
496 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
497 mainWebView.requestFocus();
501 public boolean onCreateOptionsMenu(Menu menu) {
502 // Inflate the menu; this adds items to the action bar if it is present.
503 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
505 // Set mainMenu so it can be used by onOptionsItemSelected.
508 // Initialize privacyIcon
509 privacyIcon = menu.findItem(R.id.toggleJavaScript);
511 // Set the initial status of the privacy icon.
514 // Get MenuItems for checkable menu items.
515 MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
516 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
517 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
518 MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
520 // Set the initial status of the menu item checkboxes.
521 toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
522 toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
523 toggleDomStorage.setChecked(domStorageEnabled);
524 toggleSaveFormData.setChecked(saveFormDataEnabled);
530 public boolean onPrepareOptionsMenu(Menu menu) {
531 // Only enable Third-Party Cookies if SDK >= 21 and First-Party Cookies are enabled.
532 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
533 if ((Build.VERSION.SDK_INT >= 21) && firstPartyCookiesEnabled) {
534 toggleThirdPartyCookies.setEnabled(true);
536 toggleThirdPartyCookies.setEnabled(false);
539 // Enable DOM Storage if JavaScript is enabled.
540 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
541 toggleDomStorage.setEnabled(javaScriptEnabled);
543 // Enable Clear Cookies if there are any.
544 MenuItem clearCookies = menu.findItem(R.id.clearCookies);
545 clearCookies.setEnabled(cookieManager.hasCookies());
547 // Enable Clear Form Data is there is any.
548 MenuItem clearFormData = menu.findItem(R.id.clearFormData);
549 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
550 clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
552 // Initialize font size variables.
553 int fontSize = mainWebView.getSettings().getTextZoom();
554 String fontSizeTitle;
555 MenuItem selectedFontSizeMenuItem;
557 // Prepare the font size title and current size menu item.
560 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
561 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
565 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
566 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
570 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
571 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
575 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
576 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
580 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
581 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
585 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
586 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
590 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
591 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
595 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
596 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
600 // Set the font size title and select the current size menu item.
601 MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
602 fontSizeMenuItem.setTitle(fontSizeTitle);
603 selectedFontSizeMenuItem.setChecked(true);
605 // Only show `Refresh` if `swipeToRefresh` is disabled.
606 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
607 refreshMenuItem.setVisible(!swipeToRefreshEnabled);
609 // Run all the other default commands.
610 super.onPrepareOptionsMenu(menu);
612 // `return true` displays the menu.
617 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
618 @SuppressLint("SetJavaScriptEnabled")
619 // removeAllCookies is deprecated, but it is required for API < 21.
620 @SuppressWarnings("deprecation")
621 public boolean onOptionsItemSelected(MenuItem menuItem) {
622 int menuItemId = menuItem.getItemId();
624 // Set the commands that relate to the menu entries.
625 switch (menuItemId) {
626 case R.id.toggleJavaScript:
627 // Switch the status of javaScriptEnabled.
628 javaScriptEnabled = !javaScriptEnabled;
630 // Apply the new JavaScript status.
631 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
633 // Update the privacy icon.
636 // Display a Snackbar.
637 if (javaScriptEnabled) {
638 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
640 if (firstPartyCookiesEnabled) {
641 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
643 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
647 // Reload the WebView.
648 mainWebView.reload();
651 case R.id.toggleFirstPartyCookies:
652 // Switch the status of firstPartyCookiesEnabled.
653 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
655 // Update the menu checkbox.
656 menuItem.setChecked(firstPartyCookiesEnabled);
658 // Apply the new cookie status.
659 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
661 // Update the privacy icon.
664 // Reload the WebView.
665 mainWebView.reload();
668 case R.id.toggleThirdPartyCookies:
669 if (Build.VERSION.SDK_INT >= 21) {
670 // Switch the status of thirdPartyCookiesEnabled.
671 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
673 // Update the menu checkbox.
674 menuItem.setChecked(thirdPartyCookiesEnabled);
676 // Apply the new cookie status.
677 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
679 // Reload the WebView.
680 mainWebView.reload();
681 } // Else do nothing because SDK < 21.
684 case R.id.toggleDomStorage:
685 // Switch the status of domStorageEnabled.
686 domStorageEnabled = !domStorageEnabled;
688 // Update the menu checkbox.
689 menuItem.setChecked(domStorageEnabled);
691 // Apply the new DOM Storage status.
692 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
694 // Reload the WebView.
695 mainWebView.reload();
698 case R.id.toggleSaveFormData:
699 // Switch the status of saveFormDataEnabled.
700 saveFormDataEnabled = !saveFormDataEnabled;
702 // Update the menu checkbox.
703 menuItem.setChecked(saveFormDataEnabled);
705 // Apply the new form data status.
706 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
708 // Reload the WebView.
709 mainWebView.reload();
712 case R.id.clearCookies:
713 if (Build.VERSION.SDK_INT < 21) {
714 cookieManager.removeAllCookie();
716 cookieManager.removeAllCookies(null);
718 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
721 case R.id.clearDomStorage:
722 WebStorage webStorage = WebStorage.getInstance();
723 webStorage.deleteAllData();
724 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
727 case R.id.clearFormData:
728 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
729 mainWebViewDatabase.clearFormData();
730 mainWebView.reload();
733 case R.id.fontSizeFiftyPercent:
734 mainWebView.getSettings().setTextZoom(50);
737 case R.id.fontSizeSeventyFivePercent:
738 mainWebView.getSettings().setTextZoom(75);
741 case R.id.fontSizeOneHundredPercent:
742 mainWebView.getSettings().setTextZoom(100);
745 case R.id.fontSizeOneHundredTwentyFivePercent:
746 mainWebView.getSettings().setTextZoom(125);
749 case R.id.fontSizeOneHundredFiftyPercent:
750 mainWebView.getSettings().setTextZoom(150);
753 case R.id.fontSizeOneHundredSeventyFivePercent:
754 mainWebView.getSettings().setTextZoom(175);
757 case R.id.fontSizeTwoHundredPercent:
758 mainWebView.getSettings().setTextZoom(200);
762 Intent shareIntent = new Intent();
763 shareIntent.setAction(Intent.ACTION_SEND);
764 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
765 shareIntent.setType("text/plain");
766 startActivity(Intent.createChooser(shareIntent, "Share URL"));
769 case R.id.addToHomescreen:
770 // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `@string/create_shortcut`.
771 DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
772 createHomeScreenShortcutDialogFragment.show(getFragmentManager(), getResources().getString(R.string.create_shortcut));
774 //Everything else will be handled by CreateHomeScreenShortcut and the associated listeners below.
778 mainWebView.reload();
782 // Don't consume the event.
783 return super.onOptionsItemSelected(menuItem);
788 // removeAllCookies is deprecated, but it is required for API < 21.
789 @SuppressWarnings("deprecation")
790 public boolean onNavigationItemSelected(MenuItem menuItem) {
791 int menuItemId = menuItem.getItemId();
793 switch (menuItemId) {
795 mainWebView.loadUrl(homepage, customHeaders);
799 if (mainWebView.canGoBack()) {
800 mainWebView.goBack();
805 if (mainWebView.canGoForward()) {
806 mainWebView.goForward();
811 // Launch BookmarksActivity.
812 Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
813 startActivity(bookmarksIntent);
817 // Launch the system Download Manager.
818 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
820 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
821 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
823 startActivity(downloadManagerIntent);
827 // Launch `SettingsActivity`.
828 Intent settingsIntent = new Intent(this, SettingsActivity.class);
829 startActivity(settingsIntent);
833 // Launch `GuideActivity`.
834 Intent guideIntent = new Intent(this, GuideActivity.class);
835 startActivity(guideIntent);
839 // Launch `AboutActivity`.
840 Intent aboutIntent = new Intent(this, AboutActivity.class);
841 startActivity(aboutIntent);
844 case R.id.clearAndExit:
845 // Clear cookies. The commands changed slightly in API 21.
846 if (Build.VERSION.SDK_INT >= 21) {
847 cookieManager.removeAllCookies(null);
849 cookieManager.removeAllCookie();
852 // Clear DOM storage.
853 WebStorage domStorage = WebStorage.getInstance();
854 domStorage.deleteAllData();
857 WebViewDatabase formData = WebViewDatabase.getInstance(this);
858 formData.clearFormData();
860 // Clear cache. The argument of "true" includes disk files.
861 mainWebView.clearCache(true);
863 // Clear the back/forward history.
864 mainWebView.clearHistory();
866 // Clear any SSL certificate preferences.
867 MainWebViewActivity.mainWebView.clearSslPreferences();
869 // Clear `formattedUrlString`.
870 formattedUrlString = null;
872 // Destroy the internal state of the webview.
873 mainWebView.destroy();
875 // Close Privacy Browser. finishAndRemoveTask also removes Privacy Browser from the recent app list.
876 if (Build.VERSION.SDK_INT >= 21) {
877 finishAndRemoveTask();
887 // Close the navigation drawer.
888 drawerLayout.closeDrawer(GravityCompat.START);
893 public void onPostCreate(Bundle savedInstanceState) {
894 super.onPostCreate(savedInstanceState);
896 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
897 drawerToggle.syncState();
901 public void onConfigurationChanged(Configuration newConfig) {
902 super.onConfigurationChanged(newConfig);
904 // Reload the ad if this is the free flavor.
905 BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
907 // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor by BannerAd.reloadAfterRotate().
908 adView = findViewById(R.id.adView);
912 public void onCancelCreateHomeScreenShortcut(DialogFragment dialogFragment) {
913 // Do nothing because the user selected "Cancel".
917 public void onCreateHomeScreenShortcut(DialogFragment dialogFragment) {
918 // Get shortcutNameEditText from the alert dialog.
919 EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
921 // Create the bookmark shortcut based on formattedUrlString.
922 Intent bookmarkShortcut = new Intent();
923 bookmarkShortcut.setAction(Intent.ACTION_VIEW);
924 bookmarkShortcut.setData(Uri.parse(formattedUrlString));
926 // Place the bookmark shortcut on the home screen.
927 Intent placeBookmarkShortcut = new Intent();
928 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
929 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
930 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
931 placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
932 sendBroadcast(placeBookmarkShortcut);
935 public void viewSslCertificate(View view) {
936 // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
937 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate();
938 viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
942 public void onSslErrorCancel() {
943 sslErrorHandler.cancel();
947 public void onSslErrorProceed() {
948 sslErrorHandler.proceed();
951 // Override onBackPressed to handle the navigation drawer and mainWebView.
953 public void onBackPressed() {
954 final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
956 // Close the navigation drawer if it is available. GravityCompat.START is the drawer on the left on Left-to-Right layout text.
957 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
958 drawerLayout.closeDrawer(GravityCompat.START);
960 // Load the previous URL if available.
961 assert mainWebView != null; //This assert removes the incorrect warning in Android Studio on the following line that mainWebView might be null.
962 if (mainWebView.canGoBack()) {
963 mainWebView.goBack();
965 // Pass onBackPressed to the system.
966 super.onBackPressed();
972 public void onPause() {
973 // We need to pause the adView or it will continue to consume resources in the background on the free flavor.
974 BannerAd.pauseAd(adView);
980 public void onResume() {
983 // We need to resume the adView for the free flavor.
984 BannerAd.resumeAd(adView);
987 private void loadUrlFromTextBox() throws UnsupportedEncodingException {
988 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
989 String unformattedUrlString = urlTextBox.getText().toString().trim();
991 URL unformattedUrl = null;
992 Uri.Builder formattedUri = new Uri.Builder();
994 // Check to see if unformattedUrlString is a valid URL. Otherwise, convert it into a Duck Duck Go search.
995 if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
996 // Add http:// at the beginning if it is missing. Otherwise the app will segfault.
997 if (!unformattedUrlString.startsWith("http")) {
998 unformattedUrlString = "http://" + unformattedUrlString;
1001 // Convert unformattedUrlString to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components.
1003 unformattedUrl = new URL(unformattedUrlString);
1004 } catch (MalformedURLException e) {
1005 e.printStackTrace();
1008 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1009 final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1010 final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1011 final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1012 final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1013 final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1015 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1016 formattedUrlString = formattedUri.build().toString();
1018 // Sanitize the search input and convert it to a DuckDuckGo search.
1019 final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1021 // Use the correct search URL based on javaScriptEnabled.
1022 if (javaScriptEnabled) {
1023 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1024 } else { // JavaScript is disabled.
1025 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1029 mainWebView.loadUrl(formattedUrlString, customHeaders);
1031 // Hides the keyboard so we can see the webpage.
1032 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
1033 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1036 private void updatePrivacyIcon() {
1037 if (javaScriptEnabled) {
1038 privacyIcon.setIcon(R.drawable.javascript_enabled);
1040 if (firstPartyCookiesEnabled) {
1041 privacyIcon.setIcon(R.drawable.warning);
1043 privacyIcon.setIcon(R.drawable.privacy_mode);