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.DialogFragment;
24 import android.app.DownloadManager;
25 import android.content.Context;
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.SslCertificate;
34 import android.net.http.SslError;
35 import android.os.Build;
36 import android.os.Bundle;
37 import android.preference.PreferenceManager;
38 import android.print.PrintDocumentAdapter;
39 import android.print.PrintManager;
40 import android.support.annotation.NonNull;
41 import android.support.design.widget.NavigationView;
42 import android.support.design.widget.Snackbar;
43 import android.support.v4.app.ActivityCompat;
44 import android.support.v4.content.ContextCompat;
45 import android.support.v4.view.GravityCompat;
46 import android.support.v4.widget.DrawerLayout;
47 import android.support.v4.widget.SwipeRefreshLayout;
48 import android.support.v7.app.ActionBar;
49 import android.support.v7.app.ActionBarDrawerToggle;
50 import android.support.v7.app.AppCompatActivity;
51 import android.support.v7.app.AppCompatDialogFragment;
52 import android.support.v7.widget.Toolbar;
53 import android.text.Editable;
54 import android.text.TextWatcher;
55 import android.util.Patterns;
56 import android.view.KeyEvent;
57 import android.view.Menu;
58 import android.view.MenuItem;
59 import android.view.View;
60 import android.view.inputmethod.InputMethodManager;
61 import android.webkit.CookieManager;
62 import android.webkit.DownloadListener;
63 import android.webkit.SslErrorHandler;
64 import android.webkit.WebChromeClient;
65 import android.webkit.WebStorage;
66 import android.webkit.WebView;
67 import android.webkit.WebViewClient;
68 import android.webkit.WebViewDatabase;
69 import android.widget.EditText;
70 import android.widget.FrameLayout;
71 import android.widget.ImageView;
72 import android.widget.LinearLayout;
73 import android.widget.ProgressBar;
74 import android.widget.RelativeLayout;
75 import android.widget.TextView;
77 import java.io.UnsupportedEncodingException;
78 import java.net.MalformedURLException;
80 import java.net.URLEncoder;
81 import java.util.HashMap;
84 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
85 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener,
86 SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener {
88 // `appBar` is public static so it can be accessed from `OrbotProxyHelper`.
89 // It is also used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
90 public static ActionBar appBar;
92 // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
93 // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
94 public static Bitmap favoriteIcon;
96 // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`.
97 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
98 public static String formattedUrlString;
100 // `sslCertificate` is public static so it can be accessed from `ViewSslCertificate`. It is also used in `onCreate()`.
101 public static SslCertificate sslCertificate;
104 // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage`, and `loadUrlFromTextBox()`.
105 private WebView mainWebView;
107 // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
108 private SwipeRefreshLayout swipeRefreshLayout;
110 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, and `onRestart()`.
111 private CookieManager cookieManager;
113 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
114 private final Map<String, String> customHeaders = new HashMap<>();
116 // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`.
117 // It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`.
118 private Boolean javaScriptEnabled;
120 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
121 private boolean firstPartyCookiesEnabled;
123 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
124 private boolean thirdPartyCookiesEnabled;
126 // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
127 private boolean domStorageEnabled;
129 // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
130 private boolean saveFormDataEnabled;
132 // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applySettings()`.
133 private boolean swipeToRefreshEnabled;
135 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applySettings()`.
136 private String homepage;
138 // `javaScriptDisabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
139 private String javaScriptDisabledSearchURL;
141 // `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
142 private String javaScriptEnabledSearchURL;
144 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
145 private Menu mainMenu;
147 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
148 private ActionBarDrawerToggle drawerToggle;
150 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
151 private DrawerLayout drawerLayout;
153 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
154 private EditText urlTextBox;
156 // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
159 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
160 private SslErrorHandler sslErrorHandler;
162 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
163 private EditText findOnPageEditText;
165 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
166 private InputMethodManager inputMethodManager;
169 // 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.
170 @SuppressLint("SetJavaScriptEnabled")
171 protected void onCreate(Bundle savedInstanceState) {
172 super.onCreate(savedInstanceState);
173 setContentView(R.layout.main_coordinatorlayout);
175 // Get a handle for `inputMethodManager`.
176 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
178 // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
179 Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
180 setSupportActionBar(supportAppBar);
181 appBar = getSupportActionBar();
183 // This is needed to get rid of the Android Studio warning that appBar might be null.
184 assert appBar != null;
186 // Add the custom url_app_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
187 appBar.setCustomView(R.layout.url_app_bar);
188 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
190 // Set the "go" button on the keyboard to load the URL in urlTextBox.
191 urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
192 urlTextBox.setOnKeyListener(new View.OnKeyListener() {
194 public boolean onKey(View v, int keyCode, KeyEvent event) {
195 // If the event is a key-down event on the `enter` button, load the URL.
196 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
197 // Load the URL into the mainWebView and consume the event.
199 loadUrlFromTextBox();
200 } catch (UnsupportedEncodingException e) {
203 // If the enter key was pressed, consume the event.
206 // If any other key was pressed, do not consume the event.
212 // Get handles for `fullScreenVideoFrameLayout`, `mainWebView`, and `find_on_page_edittext`.
213 final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
214 mainWebView = (WebView) findViewById(R.id.mainWebView);
215 findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext);
217 // Update `findOnPageCountTextView`.
218 mainWebView.setFindListener(new WebView.FindListener() {
219 // Get a handle for `findOnPageCountTextView`.
220 TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
223 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
224 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
225 // Set `findOnPageCountTextView` to `0/0`.
226 findOnPageCountTextView.setText(R.string.zero_of_zero);
227 } else if (isDoneCounting) { // There are matches.
228 // `activeMatchOrdinal` is zero-based.
229 int activeMatch = activeMatchOrdinal + 1;
231 // Set `findOnPageCountTextView`.
232 findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches);
237 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
238 findOnPageEditText.addTextChangedListener(new TextWatcher() {
240 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
245 public void onTextChanged(CharSequence s, int start, int before, int count) {
250 public void afterTextChanged(Editable s) {
251 // Search for the text in `mainWebView`.
252 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
256 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
257 findOnPageEditText.setOnKeyListener(new View.OnKeyListener() {
259 public boolean onKey(View v, int keyCode, KeyEvent event) {
260 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
261 // Hide the soft keyboard. `0` indicates no additional flags.
262 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
264 // Consume the event.
266 } else { // A different key was pressed.
267 // Do not consume the event.
273 // Implement swipe to refresh
274 swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
275 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
276 swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
278 public void onRefresh() {
279 mainWebView.reload();
283 // Create the navigation drawer.
284 drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
285 // The DrawerTitle identifies the drawer in accessibility mode.
286 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
288 // Listen for touches on the navigation menu.
289 final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
290 navigationView.setNavigationItemSelectedListener(this);
292 // drawerToggle creates the hamburger icon at the start of the AppBar.
293 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
295 mainWebView.setWebViewClient(new WebViewClient() {
296 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
297 // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
298 @SuppressWarnings("deprecation")
300 public boolean shouldOverrideUrlLoading(WebView view, String url) {
301 // Use an external email program if the link begins with "mailto:".
302 if (url.startsWith("mailto:")) {
303 // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
304 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
306 // Parse the url and set it as the data for the `Intent`.
307 emailIntent.setData(Uri.parse(url));
309 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
310 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
313 startActivity(emailIntent);
315 } else { // Load the URL in Privacy Browser.
316 mainWebView.loadUrl(url, customHeaders);
321 // Update the URL in urlTextBox when the page starts to load.
323 public void onPageStarted(WebView view, String url, Bitmap favicon) {
324 // 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.
325 formattedUrlString = url;
327 // Display the loading URL is the URL text box.
328 urlTextBox.setText(url);
331 // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load.
333 public void onPageFinished(WebView view, String url) {
334 formattedUrlString = url;
336 // Only update urlTextBox if the user is not typing in it.
337 if (!urlTextBox.hasFocus()) {
338 urlTextBox.setText(formattedUrlString);
341 // Store the SSL certificate so it can be accessed from `ViewSslCertificate`.
342 sslCertificate = mainWebView.getCertificate();
345 // Handle SSL Certificate errors.
347 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
348 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
349 sslErrorHandler = handler;
351 // Display the SSL error `AlertDialog`.
352 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error);
353 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
357 mainWebView.setWebChromeClient(new WebChromeClient() {
358 // Update the progress bar when a page is loading.
360 public void onProgressChanged(WebView view, int progress) {
361 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
362 progressBar.setProgress(progress);
363 if (progress < 100) {
364 progressBar.setVisibility(View.VISIBLE);
366 progressBar.setVisibility(View.GONE);
368 //Stop the `SwipeToRefresh` indicator if it is running
369 swipeRefreshLayout.setRefreshing(false);
373 // Set the favorite icon when it changes.
375 public void onReceivedIcon(WebView view, Bitmap icon) {
376 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
379 // Place the favorite icon in the appBar.
380 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
381 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
384 // Enter full screen video
386 public void onShowCustomView(View view, CustomViewCallback callback) {
389 // Show the fullScreenVideoFrameLayout.
390 fullScreenVideoFrameLayout.addView(view);
391 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
393 // Hide the mainWebView.
394 mainWebView.setVisibility(View.GONE);
396 // Hide the ad if this is the free flavor.
397 BannerAd.hideAd(adView);
399 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
400 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
401 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
403 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
406 // Exit full screen video
407 public void onHideCustomView() {
410 // Show the mainWebView.
411 mainWebView.setVisibility(View.VISIBLE);
413 // Show the ad if this is the free flavor.
414 BannerAd.showAd(adView);
416 // Hide the fullScreenVideoFrameLayout.
417 fullScreenVideoFrameLayout.removeAllViews();
418 fullScreenVideoFrameLayout.setVisibility(View.GONE);
422 // Allow the downloading of files.
423 mainWebView.setDownloadListener(new DownloadListener() {
425 public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
426 // Show the `DownloadFile` `AlertDialog` and name this instance `@string/download`.
427 AppCompatDialogFragment downloadFileDialogFragment = DownloadFile.fromUrl(url, contentDisposition, contentLength);
428 downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
432 // Allow pinch to zoom.
433 mainWebView.getSettings().setBuiltInZoomControls(true);
435 // Hide zoom controls.
436 mainWebView.getSettings().setDisplayZoomControls(false);
438 // Initialize cookieManager.
439 cookieManager = CookieManager.getInstance();
441 // 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).
442 customHeaders.put("X-Requested-With", "");
444 // Initialize the default preference values the first time the program is run.
445 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
447 // Apply the settings from the shared preferences.
450 // Get the intent information that started the app.
451 final Intent intent = getIntent();
453 if (intent.getData() != null) {
454 // Get the intent data and convert it to a string.
455 final Uri intentUriData = intent.getData();
456 formattedUrlString = intentUriData.toString();
459 // If formattedUrlString is null assign the homepage to it.
460 if (formattedUrlString == null) {
461 formattedUrlString = homepage;
464 // Load the initial website.
465 mainWebView.loadUrl(formattedUrlString, customHeaders);
467 // If the favorite icon is null, load the default.
468 if (favoriteIcon == null) {
469 // We have to use `ContextCompat` until API >= 21.
470 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
471 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
472 favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
475 // Initialize AdView for the free flavor and request an ad. If this is not the free flavor BannerAd.requestAd() does nothing.
476 adView = findViewById(R.id.adView);
477 BannerAd.requestAd(adView);
482 protected void onNewIntent(Intent intent) {
483 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
486 if (intent.getData() != null) {
487 // Get the intent data and convert it to a string.
488 final Uri intentUriData = intent.getData();
489 formattedUrlString = intentUriData.toString();
492 // Close the navigation drawer if it is open.
493 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
494 drawerLayout.closeDrawer(GravityCompat.START);
498 mainWebView.loadUrl(formattedUrlString, customHeaders);
500 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
501 mainWebView.requestFocus();
505 public boolean onCreateOptionsMenu(Menu menu) {
506 // Inflate the menu; this adds items to the action bar if it is present.
507 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
509 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
512 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
513 updatePrivacyIcons(false);
515 // Get handles for the menu items.
516 MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
517 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
518 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
519 MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
521 // Only display third-Party Cookies if SDK >= 21
522 toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21);
524 // Get the shared preference values. `this` references the current context.
525 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
527 // Set the status of the additional app bar icons. The default is `false`.
528 if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
529 toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
530 toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
531 toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
532 } else { //Do not display the additional icons.
533 toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
534 toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
535 toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
542 public boolean onPrepareOptionsMenu(Menu menu) {
543 // Get handles for the menu items.
544 MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
545 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
546 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
547 MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
548 MenuItem clearCookies = menu.findItem(R.id.clearCookies);
549 MenuItem clearFormData = menu.findItem(R.id.clearFormData);
550 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
552 // Set the status of the menu item checkboxes.
553 toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
554 toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
555 toggleDomStorage.setChecked(domStorageEnabled);
556 toggleSaveFormData.setChecked(saveFormDataEnabled);
558 // Enable third-party cookies if first-party cookies are enabled.
559 toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled);
561 // Enable DOM Storage if JavaScript is enabled.
562 toggleDomStorage.setEnabled(javaScriptEnabled);
564 // Enable Clear Cookies if there are any.
565 clearCookies.setEnabled(cookieManager.hasCookies());
567 // Enable Clear Form Data is there is any.
568 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
569 clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
571 // Only show `Refresh` if `swipeToRefresh` is disabled.
572 refreshMenuItem.setVisible(!swipeToRefreshEnabled);
574 // Initialize font size variables.
575 int fontSize = mainWebView.getSettings().getTextZoom();
576 String fontSizeTitle;
577 MenuItem selectedFontSizeMenuItem;
579 // Prepare the font size title and current size menu item.
582 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
583 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
587 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
588 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
592 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
593 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
597 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
598 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
602 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
603 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
607 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
608 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
612 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
613 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
617 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
618 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
622 // Set the font size title and select the current size menu item.
623 MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
624 fontSizeMenuItem.setTitle(fontSizeTitle);
625 selectedFontSizeMenuItem.setChecked(true);
627 // Run all the other default commands.
628 super.onPrepareOptionsMenu(menu);
630 // `return true` displays the menu.
635 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
636 @SuppressLint("SetJavaScriptEnabled")
637 // removeAllCookies is deprecated, but it is required for API < 21.
638 @SuppressWarnings("deprecation")
639 public boolean onOptionsItemSelected(MenuItem menuItem) {
640 int menuItemId = menuItem.getItemId();
642 // Set the commands that relate to the menu entries.
643 switch (menuItemId) {
644 case R.id.toggleJavaScript:
645 // Switch the status of javaScriptEnabled.
646 javaScriptEnabled = !javaScriptEnabled;
648 // Apply the new JavaScript status.
649 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
651 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
652 updatePrivacyIcons(true);
654 // Display a `Snackbar`.
655 if (javaScriptEnabled) { // JavaScrip is enabled.
656 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
657 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
658 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
659 } else { // Privacy mode.
660 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
663 // Reload the WebView.
664 mainWebView.reload();
667 case R.id.toggleFirstPartyCookies:
668 // Switch the status of firstPartyCookiesEnabled.
669 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
671 // Update the menu checkbox.
672 menuItem.setChecked(firstPartyCookiesEnabled);
674 // Apply the new cookie status.
675 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
677 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
678 updatePrivacyIcons(true);
680 // Display a `Snackbar`.
681 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
682 Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
683 } else if (javaScriptEnabled){ // JavaScript is still enabled.
684 Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
685 } else { // Privacy mode.
686 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
689 // Reload the WebView.
690 mainWebView.reload();
693 case R.id.toggleThirdPartyCookies:
694 if (Build.VERSION.SDK_INT >= 21) {
695 // Switch the status of thirdPartyCookiesEnabled.
696 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
698 // Update the menu checkbox.
699 menuItem.setChecked(thirdPartyCookiesEnabled);
701 // Apply the new cookie status.
702 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
704 // Display a `Snackbar`.
705 if (thirdPartyCookiesEnabled) {
706 Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
708 Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
711 // Reload the WebView.
712 mainWebView.reload();
713 } // Else do nothing because SDK < 21.
716 case R.id.toggleDomStorage:
717 // Switch the status of domStorageEnabled.
718 domStorageEnabled = !domStorageEnabled;
720 // Update the menu checkbox.
721 menuItem.setChecked(domStorageEnabled);
723 // Apply the new DOM Storage status.
724 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
726 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
727 updatePrivacyIcons(true);
729 // Display a `Snackbar`.
730 if (domStorageEnabled) {
731 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
733 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
736 // Reload the WebView.
737 mainWebView.reload();
740 case R.id.toggleSaveFormData:
741 // Switch the status of saveFormDataEnabled.
742 saveFormDataEnabled = !saveFormDataEnabled;
744 // Update the menu checkbox.
745 menuItem.setChecked(saveFormDataEnabled);
747 // Apply the new form data status.
748 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
750 // Display a `Snackbar`.
751 if (saveFormDataEnabled) {
752 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
754 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
757 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
758 updatePrivacyIcons(true);
760 // Reload the WebView.
761 mainWebView.reload();
764 case R.id.clearCookies:
765 if (Build.VERSION.SDK_INT < 21) {
766 cookieManager.removeAllCookie();
768 cookieManager.removeAllCookies(null);
770 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
773 case R.id.clearDomStorage:
774 WebStorage webStorage = WebStorage.getInstance();
775 webStorage.deleteAllData();
776 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
779 case R.id.clearFormData:
780 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
781 mainWebViewDatabase.clearFormData();
782 mainWebView.reload();
785 case R.id.fontSizeFiftyPercent:
786 mainWebView.getSettings().setTextZoom(50);
789 case R.id.fontSizeSeventyFivePercent:
790 mainWebView.getSettings().setTextZoom(75);
793 case R.id.fontSizeOneHundredPercent:
794 mainWebView.getSettings().setTextZoom(100);
797 case R.id.fontSizeOneHundredTwentyFivePercent:
798 mainWebView.getSettings().setTextZoom(125);
801 case R.id.fontSizeOneHundredFiftyPercent:
802 mainWebView.getSettings().setTextZoom(150);
805 case R.id.fontSizeOneHundredSeventyFivePercent:
806 mainWebView.getSettings().setTextZoom(175);
809 case R.id.fontSizeTwoHundredPercent:
810 mainWebView.getSettings().setTextZoom(200);
813 case R.id.find_on_page:
814 // Hide the URL app bar.
815 Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar);
816 appBarToolbar.setVisibility(View.GONE);
818 // Show the Find on Page `RelativeLayout`.
819 LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
820 findOnPageLinearLayout.setVisibility(View.VISIBLE);
822 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
823 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
824 findOnPageEditText.postDelayed(new Runnable()
829 // Set the focus on `findOnPageEditText`.
830 findOnPageEditText.requestFocus();
832 // Display the keyboard.
833 inputMethodManager.showSoftInput(findOnPageEditText, 0);
839 Intent shareIntent = new Intent();
840 shareIntent.setAction(Intent.ACTION_SEND);
841 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
842 shareIntent.setType("text/plain");
843 startActivity(Intent.createChooser(shareIntent, "Share URL"));
846 case R.id.addToHomescreen:
847 // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `@string/create_shortcut`.
848 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
849 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut));
851 //Everything else will be handled by `CreateHomeScreenShortcut` and the associated listener below.
855 // Get a `PrintManager` instance.
856 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
858 // Convert `mainWebView` to `printDocumentAdapter`.
859 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
861 // Print the document. The print attributes are `null`.
862 printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
866 mainWebView.reload();
870 // Don't consume the event.
871 return super.onOptionsItemSelected(menuItem);
875 // removeAllCookies is deprecated, but it is required for API < 21.
876 @SuppressWarnings("deprecation")
878 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
879 int menuItemId = menuItem.getItemId();
881 switch (menuItemId) {
883 mainWebView.loadUrl(homepage, customHeaders);
887 if (mainWebView.canGoBack()) {
888 mainWebView.goBack();
893 if (mainWebView.canGoForward()) {
894 mainWebView.goForward();
899 // Launch BookmarksActivity.
900 Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
901 startActivity(bookmarksIntent);
905 // Launch the system Download Manager.
906 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
908 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
909 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
911 startActivity(downloadManagerIntent);
915 // Launch `SettingsActivity`.
916 Intent settingsIntent = new Intent(this, SettingsActivity.class);
917 startActivity(settingsIntent);
921 // Launch `GuideActivity`.
922 Intent guideIntent = new Intent(this, GuideActivity.class);
923 startActivity(guideIntent);
927 // Launch `AboutActivity`.
928 Intent aboutIntent = new Intent(this, AboutActivity.class);
929 startActivity(aboutIntent);
932 case R.id.clearAndExit:
933 // Clear cookies. The commands changed slightly in API 21.
934 if (Build.VERSION.SDK_INT >= 21) {
935 cookieManager.removeAllCookies(null);
937 cookieManager.removeAllCookie();
940 // Clear DOM storage.
941 WebStorage domStorage = WebStorage.getInstance();
942 domStorage.deleteAllData();
945 WebViewDatabase formData = WebViewDatabase.getInstance(this);
946 formData.clearFormData();
948 // Clear cache. The argument of "true" includes disk files.
949 mainWebView.clearCache(true);
951 // Clear the back/forward history.
952 mainWebView.clearHistory();
954 // Clear any SSL certificate preferences.
955 mainWebView.clearSslPreferences();
957 // Clear `formattedUrlString`.
958 formattedUrlString = null;
960 // Clear `customHeaders`.
961 customHeaders.clear();
963 // Detach all views from `mainWebViewRelativeLayout`.
964 RelativeLayout mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.mainWebViewRelativeLayout);
965 mainWebViewRelativeLayout.removeAllViews();
967 // Destroy the internal state of `mainWebView`.
968 mainWebView.destroy();
970 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
971 if (Build.VERSION.SDK_INT >= 21) {
972 finishAndRemoveTask();
977 // Remove the terminated program from RAM. The status code is `0`.
985 // Close the navigation drawer.
986 drawerLayout.closeDrawer(GravityCompat.START);
991 public void onPostCreate(Bundle savedInstanceState) {
992 super.onPostCreate(savedInstanceState);
994 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
995 drawerToggle.syncState();
999 public void onConfigurationChanged(Configuration newConfig) {
1000 super.onConfigurationChanged(newConfig);
1002 // Reload the ad if this is the free flavor.
1003 BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
1005 // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor by BannerAd.reloadAfterRotate().
1006 adView = findViewById(R.id.adView);
1008 // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug: https://code.google.com/p/android/issues/detail?id=20493#c8
1009 // ActivityCompat.invalidateOptionsMenu(this);
1013 public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
1014 // Get shortcutNameEditText from the alert dialog.
1015 EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
1017 // Create the bookmark shortcut based on formattedUrlString.
1018 Intent bookmarkShortcut = new Intent();
1019 bookmarkShortcut.setAction(Intent.ACTION_VIEW);
1020 bookmarkShortcut.setData(Uri.parse(formattedUrlString));
1022 // Place the bookmark shortcut on the home screen.
1023 Intent placeBookmarkShortcut = new Intent();
1024 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
1025 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
1026 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
1027 placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1028 sendBroadcast(placeBookmarkShortcut);
1032 public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
1033 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
1034 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
1036 // Get the file name from `dialogFragment`.
1037 EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
1038 String fileName = downloadFileNameEditText.getText().toString();
1040 // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
1041 if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `fileName`.
1042 downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
1043 } else { // Only set the title using `fileName`.
1044 downloadRequest.setTitle(fileName);
1047 // Allow `MediaScanner` to index the download if it is a media file.
1048 downloadRequest.allowScanningByMediaScanner();
1050 // Add the URL as the description for the download.
1051 downloadRequest.setDescription(downloadUrl);
1053 // Show the download notification after the download is completed.
1054 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1056 // Initiate the download and display a Snackbar.
1057 downloadManager.enqueue(downloadRequest);
1060 public void viewSslCertificate(View view) {
1061 // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
1062 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate();
1063 viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
1067 public void onSslErrorCancel() {
1068 sslErrorHandler.cancel();
1072 public void onSslErrorProceed() {
1073 sslErrorHandler.proceed();
1076 // Override onBackPressed to handle the navigation drawer and mainWebView.
1078 public void onBackPressed() {
1079 final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
1081 // Close the navigation drawer if it is available. GravityCompat.START is the drawer on the left on Left-to-Right layout text.
1082 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1083 drawerLayout.closeDrawer(GravityCompat.START);
1085 // Load the previous URL if available.
1086 if (mainWebView.canGoBack()) {
1087 mainWebView.goBack();
1089 // Pass onBackPressed to the system.
1090 super.onBackPressed();
1096 public void onPause() {
1097 // We need to pause the adView or it will continue to consume resources in the background on the free flavor.
1098 BannerAd.pauseAd(adView);
1104 public void onResume() {
1107 // We need to resume the adView for the free flavor.
1108 BannerAd.resumeAd(adView);
1112 public void onRestart() {
1115 // Apply the settings from shared preferences, which might have been changed in `SettingsActivity`.
1118 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1119 updatePrivacyIcons(true);
1123 private void loadUrlFromTextBox() throws UnsupportedEncodingException {
1124 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
1125 String unformattedUrlString = urlTextBox.getText().toString().trim();
1127 URL unformattedUrl = null;
1128 Uri.Builder formattedUri = new Uri.Builder();
1130 // Check to see if unformattedUrlString is a valid URL. Otherwise, convert it into a Duck Duck Go search.
1131 if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
1132 // Add http:// at the beginning if it is missing. Otherwise the app will segfault.
1133 if (!unformattedUrlString.startsWith("http")) {
1134 unformattedUrlString = "http://" + unformattedUrlString;
1137 // 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.
1139 unformattedUrl = new URL(unformattedUrlString);
1140 } catch (MalformedURLException e) {
1141 e.printStackTrace();
1144 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1145 final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1146 final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1147 final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1148 final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1149 final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1151 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1152 formattedUrlString = formattedUri.build().toString();
1154 // Sanitize the search input and convert it to a DuckDuckGo search.
1155 final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1157 // Use the correct search URL.
1158 if (javaScriptEnabled) { // JavaScript is enabled.
1159 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1160 } else { // JavaScript is disabled.
1161 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1165 mainWebView.loadUrl(formattedUrlString, customHeaders);
1167 // Hides the keyboard so we can see the webpage. `0` indicates no additional flags.
1168 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1171 public void findPreviousOnPage(View view) {
1172 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
1173 mainWebView.findNext(false);
1176 public void findNextOnPage(View view) {
1177 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
1178 mainWebView.findNext(true);
1181 public void closeFindOnPage(View view) {
1182 // Delete the contents of `find_on_page_edittext`.
1183 findOnPageEditText.setText(null);
1185 // Clear the highlighted phrases.
1186 mainWebView.clearMatches();
1188 // Hide the Find on Page `RelativeLayout`.
1189 LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
1190 findOnPageLinearLayout.setVisibility(View.GONE);
1192 // Show the URL app bar.
1193 Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar);
1194 appBarToolbar.setVisibility(View.VISIBLE);
1196 // Hides the keyboard so we can see the webpage. `0` indicates no additional flags.
1197 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1200 private void applySettings() {
1201 // Get the shared preference values. `this` references the current context.
1202 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1204 // Store the values from `sharedPreferences` in variables.
1205 String userAgentString = sharedPreferences.getString("user_agent", "Default user agent");
1206 String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
1207 String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
1208 String javaScriptDisabledCustomSearchString = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
1209 String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
1210 String javaScriptEnabledCustomSearchString = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
1211 String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
1212 String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
1213 swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false);
1214 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", true);
1215 boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
1217 // Because they can be modified on-the-fly by the user, these default settings are only applied when the program first runs.
1218 if (javaScriptEnabled == null) { // If `javaScriptEnabled` is null the program is just starting.
1219 // Get the values from `sharedPreferences`.
1220 javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
1221 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
1222 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
1223 domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
1224 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
1226 // Apply the default settings.
1227 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
1228 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1229 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
1230 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
1231 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
1233 // Set third-party cookies status if API >= 21.
1234 if (Build.VERSION.SDK_INT >= 21) {
1235 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
1239 // Apply the other settings from `sharedPreferences`.
1240 homepage = homepageString;
1241 swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
1243 // Set the user agent initial status.
1244 switch (userAgentString) {
1245 case "Default user agent":
1246 // Set the user agent to `""`, which uses the default value.
1247 mainWebView.getSettings().setUserAgentString("");
1250 case "Custom user agent":
1251 // Set the custom user agent.
1252 mainWebView.getSettings().setUserAgentString(customUserAgentString);
1256 // Use the selected user agent.
1257 mainWebView.getSettings().setUserAgentString(userAgentString);
1261 // Set JavaScript disabled search.
1262 if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string.
1263 javaScriptDisabledSearchURL = javaScriptDisabledCustomSearchString;
1264 } else { // Use the string from the pre-built list.
1265 javaScriptDisabledSearchURL = javaScriptDisabledSearchString;
1268 // Set JavaScript enabled search.
1269 if (javaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string.
1270 javaScriptEnabledSearchURL = javaScriptEnabledCustomSearchString;
1271 } else { // Use the string from the pre-built list.
1272 javaScriptEnabledSearchURL = javaScriptEnabledSearchString;
1275 // Set Do Not Track status.
1276 if (doNotTrackEnabled) {
1277 customHeaders.put("DNT", "1");
1279 customHeaders.remove("DNT");
1282 // Set Orbot proxy status.
1283 if (proxyThroughOrbot) {
1284 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
1285 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
1286 } else { // Reset the proxy to default. The host is `""` and the port is `"0"`.
1287 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
1291 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
1292 // Get handles for the icons.
1293 MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript);
1294 MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies);
1295 MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage);
1296 MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData);
1298 // Update `privacyIcon`.
1299 if (javaScriptEnabled) { // JavaScript is enabled.
1300 privacyIcon.setIcon(R.drawable.javascript_enabled);
1301 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
1302 privacyIcon.setIcon(R.drawable.warning);
1303 } else { // All the dangerous features are disabled.
1304 privacyIcon.setIcon(R.drawable.privacy_mode);
1307 // Update `firstPartyCookiesIcon`.
1308 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
1309 firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled);
1310 } else { // First-party cookies are disabled.
1311 firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
1314 // Update `domStorageIcon`.
1315 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
1316 domStorageIcon.setIcon(R.drawable.dom_storage_enabled);
1317 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
1318 domStorageIcon.setIcon(R.drawable.dom_storage_disabled);
1319 } else { // JavaScript is disabled, so DOM storage is ghosted.
1320 domStorageIcon.setIcon(R.drawable.dom_storage_ghosted);
1323 // Update `formDataIcon`.
1324 if (saveFormDataEnabled) { // Form data is enabled.
1325 formDataIcon.setIcon(R.drawable.form_data_enabled);
1326 } else { // Form data is disabled.
1327 formDataIcon.setIcon(R.drawable.form_data_disabled);
1330 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. `this` references the current activity.
1331 if (runInvalidateOptionsMenu) {
1332 ActivityCompat.invalidateOptionsMenu(this);