2 * Copyright 2015-2017 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.annotation.SuppressLint;
25 import android.app.DialogFragment;
26 import android.app.DownloadManager;
27 import android.content.BroadcastReceiver;
28 import android.content.ClipData;
29 import android.content.ClipboardManager;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.SharedPreferences;
34 import android.content.res.Configuration;
35 import android.database.Cursor;
36 import android.graphics.Bitmap;
37 import android.graphics.drawable.BitmapDrawable;
38 import android.graphics.drawable.Drawable;
39 import android.net.Uri;
40 import android.net.http.SslCertificate;
41 import android.net.http.SslError;
42 import android.os.Build;
43 import android.os.Bundle;
44 import android.preference.PreferenceManager;
45 import android.print.PrintDocumentAdapter;
46 import android.print.PrintManager;
47 import android.support.annotation.NonNull;
48 import android.support.design.widget.CoordinatorLayout;
49 import android.support.design.widget.NavigationView;
50 import android.support.design.widget.Snackbar;
51 import android.support.v4.app.ActivityCompat;
52 import android.support.v4.content.ContextCompat;
53 import android.support.v4.view.GravityCompat;
54 import android.support.v4.widget.DrawerLayout;
55 import android.support.v4.widget.SwipeRefreshLayout;
56 import android.support.v7.app.ActionBar;
57 import android.support.v7.app.ActionBarDrawerToggle;
58 import android.support.v7.app.AppCompatActivity;
59 import android.support.v7.app.AppCompatDialogFragment;
60 import android.support.v7.widget.Toolbar;
61 import android.text.Editable;
62 import android.text.TextWatcher;
63 import android.util.Patterns;
64 import android.view.ContextMenu;
65 import android.view.GestureDetector;
66 import android.view.KeyEvent;
67 import android.view.Menu;
68 import android.view.MenuItem;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.view.WindowManager;
72 import android.view.inputmethod.InputMethodManager;
73 import android.webkit.CookieManager;
74 import android.webkit.DownloadListener;
75 import android.webkit.SslErrorHandler;
76 import android.webkit.WebBackForwardList;
77 import android.webkit.WebChromeClient;
78 import android.webkit.WebResourceResponse;
79 import android.webkit.WebStorage;
80 import android.webkit.WebView;
81 import android.webkit.WebViewClient;
82 import android.webkit.WebViewDatabase;
83 import android.widget.EditText;
84 import android.widget.FrameLayout;
85 import android.widget.ImageView;
86 import android.widget.LinearLayout;
87 import android.widget.ProgressBar;
88 import android.widget.RelativeLayout;
89 import android.widget.TextView;
91 import com.stoutner.privacybrowser.BannerAd;
92 import com.stoutner.privacybrowser.BuildConfig;
93 import com.stoutner.privacybrowser.R;
94 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
95 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
96 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
97 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
98 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
99 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
100 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
101 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
103 import java.io.BufferedReader;
104 import java.io.ByteArrayInputStream;
105 import java.io.IOException;
106 import java.io.InputStreamReader;
107 import java.io.UnsupportedEncodingException;
108 import java.net.MalformedURLException;
110 import java.net.URLEncoder;
111 import java.util.HashMap;
112 import java.util.HashSet;
113 import java.util.Map;
114 import java.util.Set;
116 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
117 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener,
118 SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener {
120 // `appBar` is public static so it can be accessed from `OrbotProxyHelper`.
121 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applySettings()`.
122 public static ActionBar appBar;
124 // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, and `EditBookmarkDialog`.
125 // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
126 public static Bitmap favoriteIcon;
128 // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
129 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
130 public static String formattedUrlString;
132 // `sslCertificate` is public static so it can be accessed from `ViewSslCertificateDialog`. It is also used in `onCreate()`.
133 public static SslCertificate sslCertificate;
135 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`.
136 public static String orbotStatus;
139 // `navigatingHistory` is used in `onCreate()` and `onNavigationItemSelected()`.
140 private boolean navigatingHistory;
142 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
143 private DrawerLayout drawerLayout;
145 // `rootCoordinatorLayout` is used in `onCreate()` and `applySettings()`.
146 private CoordinatorLayout rootCoordinatorLayout;
148 // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`.
149 private WebView mainWebView;
151 // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
152 private FrameLayout fullScreenVideoFrameLayout;
154 // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
155 private SwipeRefreshLayout swipeRefreshLayout;
157 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
158 private CookieManager cookieManager;
160 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
161 private final Map<String, String> customHeaders = new HashMap<>();
163 // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`.
164 // It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`.
165 private Boolean javaScriptEnabled;
167 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applySettings()`.
168 private boolean firstPartyCookiesEnabled;
170 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
171 private boolean thirdPartyCookiesEnabled;
173 // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
174 private boolean domStorageEnabled;
176 // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
177 private boolean saveFormDataEnabled;
179 // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applySettings()`.
180 private boolean swipeToRefreshEnabled;
182 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applySettings()`.
183 private String homepage;
185 // `javaScriptDisabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
186 private String javaScriptDisabledSearchURL;
188 // `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
189 private String javaScriptEnabledSearchURL;
191 // `adBlockerEnabled` is used in `onCreate()` and `applySettings()`.
192 private boolean adBlockerEnabled;
194 // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applySettings()`.
195 private boolean fullScreenBrowsingModeEnabled;
197 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applySettings()`.
198 private boolean inFullScreenBrowsingMode;
200 // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applySettings()`.
201 private boolean hideSystemBarsOnFullscreen;
203 // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applySettings()`.
204 private boolean translucentNavigationBarOnFullscreen;
206 // `proxyThroughOrbot` is used in `onCreate()` and `applySettings()`
207 private boolean proxyThroughOrbot;
209 // `pendingUrl` is used in `onCreate()` and `applySettings()`
210 private static String pendingUrl;
212 // `waitingForOrbotData` is used in `onCreate()` and `applySettings()`.
213 private String waitingForOrbotHTMLString;
215 // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
216 private LinearLayout findOnPageLinearLayout;
218 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
219 private EditText findOnPageEditText;
221 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
222 private Menu mainMenu;
224 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
225 private ActionBarDrawerToggle drawerToggle;
227 // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
228 private Toolbar supportAppBar;
230 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `loadUrl()`.
231 private EditText urlTextBox;
233 // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
236 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
237 private SslErrorHandler sslErrorHandler;
239 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
240 private InputMethodManager inputMethodManager;
242 // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
243 private RelativeLayout mainWebViewRelativeLayout;
246 // 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.
247 @SuppressLint("SetJavaScriptEnabled")
248 protected void onCreate(Bundle savedInstanceState) {
249 super.onCreate(savedInstanceState);
250 setContentView(R.layout.drawerlayout);
252 // Get a handle for `inputMethodManager`.
253 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
255 // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
256 supportAppBar = (Toolbar) findViewById(R.id.app_bar);
257 setSupportActionBar(supportAppBar);
258 appBar = getSupportActionBar();
260 // This is needed to get rid of the Android Studio warning that `appBar` might be null.
261 assert appBar != null;
263 // Add the custom url_app_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
264 appBar.setCustomView(R.layout.url_app_bar);
265 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
267 // Set the "go" button on the keyboard to load the URL in urlTextBox.
268 urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
269 urlTextBox.setOnKeyListener(new View.OnKeyListener() {
271 public boolean onKey(View v, int keyCode, KeyEvent event) {
272 // If the event is a key-down event on the `enter` button, load the URL.
273 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
274 // Load the URL into the mainWebView and consume the event.
276 loadUrlFromTextBox();
277 } catch (UnsupportedEncodingException e) {
280 // If the enter key was pressed, consume the event.
283 // If any other key was pressed, do not consume the event.
289 // Set `waitingForOrbotHTMLString`.
290 waitingForOrbotHTMLString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
292 // Initialize `pendingUrl`.
295 // Set the initial Orbot status.
296 orbotStatus = "unknown";
298 // Create an Orbot status `BroadcastReceiver`.
299 BroadcastReceiver orbotStatusBroadcastReceiver = new BroadcastReceiver() {
301 public void onReceive(Context context, Intent intent) {
302 // Store the content of the status message in `orbotStatus`.
303 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
305 // If we are waiting on `pendingUrl`, load it now that Orbot is connected.
306 if (orbotStatus.equals("ON") && !pendingUrl.isEmpty()) {
308 // Wait 500 milliseconds, because Orbot isn't really ready yet.
311 } catch (InterruptedException exception) {
315 // Copy `pendingUrl` to `formattedUrlString` and reset `pendingUrl` to be empty.
316 formattedUrlString = pendingUrl;
319 // Load `formattedUrlString
320 loadUrl(formattedUrlString);
325 // Register `orbotStatusBroadcastReceiver` on `this` context.
326 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
328 // Get handles for views that need to be accessed.
329 drawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout);
330 rootCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.root_coordinatorlayout);
331 mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.main_webview_relativelayout);
332 mainWebView = (WebView) findViewById(R.id.mainWebView);
333 findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
334 findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext);
335 fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.full_screen_video_framelayout);
337 // Create a double-tap listener to toggle full-screen mode.
338 final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
339 // Override `onDoubleTap()`. All other events are handled using the default settings.
341 public boolean onDoubleTap(MotionEvent event) {
342 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
343 // Toggle `inFullScreenBrowsingMode`.
344 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
346 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
347 // Hide the `appBar`.
350 // Hide the `BannerAd` in the free flavor.
351 if (BuildConfig.FLAVOR.contentEquals("free")) {
352 BannerAd.hideAd(adView);
355 // Modify the system bars.
356 if (hideSystemBarsOnFullscreen) { // Hide everything.
357 // Remove the translucent overlays.
358 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
360 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
361 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
363 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
364 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
365 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
367 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
369 // Set `rootCoordinatorLayout` to fill the whole screen.
370 rootCoordinatorLayout.setFitsSystemWindows(false);
371 } else { // Hide everything except the status and navigation bars.
372 // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
373 rootCoordinatorLayout.setFitsSystemWindows(false);
375 if (translucentNavigationBarOnFullscreen) { // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
376 // Set the navigation bar to be translucent.
377 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
380 } else { // Switch to normal viewing mode.
381 // Show the `appBar`.
384 // Show the `BannerAd` in the free flavor.
385 if (BuildConfig.FLAVOR.contentEquals("free")) {
386 // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`.
387 BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
389 // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
390 adView = findViewById(R.id.adView);
393 // Remove the translucent navigation bar flag if it is set.
394 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
396 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
397 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
399 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
400 rootCoordinatorLayout.setSystemUiVisibility(0);
402 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
403 rootCoordinatorLayout.setFitsSystemWindows(true);
406 // Consume the double-tap.
408 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
414 // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
415 mainWebView.setOnTouchListener(new View.OnTouchListener() {
417 public boolean onTouch(View v, MotionEvent event) {
418 // Send the `event` to `gestureDetector`.
419 return gestureDetector.onTouchEvent(event);
423 // Update `findOnPageCountTextView`.
424 mainWebView.setFindListener(new WebView.FindListener() {
425 // Get a handle for `findOnPageCountTextView`.
426 final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
429 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
430 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
431 // Set `findOnPageCountTextView` to `0/0`.
432 findOnPageCountTextView.setText(R.string.zero_of_zero);
433 } else if (isDoneCounting) { // There are matches.
434 // `activeMatchOrdinal` is zero-based.
435 int activeMatch = activeMatchOrdinal + 1;
437 // Set `findOnPageCountTextView`.
438 findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches);
443 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
444 findOnPageEditText.addTextChangedListener(new TextWatcher() {
446 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
451 public void onTextChanged(CharSequence s, int start, int before, int count) {
456 public void afterTextChanged(Editable s) {
457 // Search for the text in `mainWebView`.
458 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
462 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
463 findOnPageEditText.setOnKeyListener(new View.OnKeyListener() {
465 public boolean onKey(View v, int keyCode, KeyEvent event) {
466 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
467 // Hide the soft keyboard. `0` indicates no additional flags.
468 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
470 // Consume the event.
472 } else { // A different key was pressed.
473 // Do not consume the event.
479 // Implement swipe to refresh
480 swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
481 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
482 swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
484 public void onRefresh() {
485 mainWebView.reload();
489 // `DrawerTitle` identifies the `DrawerLayout` in accessibility mode.
490 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
492 // Listen for touches on the navigation menu.
493 final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationview);
494 navigationView.setNavigationItemSelectedListener(this);
496 // Get handles for `navigationMenu` and the back and forward menu items. The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu.
497 final Menu navigationMenu = navigationView.getMenu();
498 final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
499 final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
500 final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
502 // The `DrawerListener` allows us to update the Navigation Menu.
503 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
505 public void onDrawerSlide(View drawerView, float slideOffset) {
509 public void onDrawerOpened(View drawerView) {
513 public void onDrawerClosed(View drawerView) {
517 public void onDrawerStateChanged(int newState) {
518 // Update the `Back`, `Forward`, and `History` menu items every time the drawer opens.
519 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
520 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
521 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
523 // Hide the keyboard so we can see the navigation menu. `0` indicates no additional flags.
524 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
528 // drawerToggle creates the hamburger icon at the start of the AppBar.
529 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
531 // Initialize `adServerSet`.
532 final Set<String> adServersSet = new HashSet<>();
534 // Load the list of ad servers into memory.
536 // Load `pgl.yoyo.org_adservers.txt` into a `BufferedReader`.
537 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("pgl.yoyo.org_adservers.txt")));
539 // Create a string for storing each ad server.
542 // Populate `adServersSet`.
543 while ((adServer = bufferedReader.readLine()) != null) {
544 adServersSet.add(adServer);
547 // Close `bufferedReader`.
548 bufferedReader.close();
549 } catch (IOException ioException) {
550 // We're pretty sure the asset exists, so we don't need to worry about the `IOException` ever being thrown.
553 mainWebView.setWebViewClient(new WebViewClient() {
554 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
555 // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
556 @SuppressWarnings("deprecation")
558 public boolean shouldOverrideUrlLoading(WebView view, String url) {
559 // Use an external email program if the link begins with `mailto:`.
560 if (url.startsWith("mailto:")) {
561 // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
562 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
564 // Parse the url and set it as the data for the `Intent`.
565 emailIntent.setData(Uri.parse(url));
567 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
568 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
571 startActivity(emailIntent);
573 } else { // Load the URL in Privacy Browser.
579 // Block ads. We have to use the deprecated `shouldInterceptRequest` until minimum API >= 21.
580 @SuppressWarnings("deprecation")
582 public WebResourceResponse shouldInterceptRequest(WebView view, String url){
583 if (adBlockerEnabled) { // Block ads.
584 // Extract the host from `url`.
585 Uri requestUri = Uri.parse(url);
586 String requestHost = requestUri.getHost();
588 // Initialize a variable to track if this is an ad server.
589 boolean requestHostIsAdServer = false;
591 // Check all the subdomains of `requestHost` if it is not `null` against the ad server database.
592 if (requestHost != null) {
593 while (requestHost.contains(".") && !requestHostIsAdServer) { // Stop checking if we run out of `.` or if we already know that `requestHostIsAdServer` is `true`.
594 if (adServersSet.contains(requestHost)) {
595 requestHostIsAdServer = true;
598 // Strip out the lowest subdomain of `requestHost`.
599 requestHost = requestHost.substring(requestHost.indexOf(".") + 1);
603 if (requestHostIsAdServer) { // It is an ad server.
604 // Return an empty `WebResourceResponse`.
605 return new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
606 } else { // It is not an ad server.
607 // `return null` loads the requested resource.
610 } else { // Ad blocking is disabled.
611 // `return null` loads the requested resource.
616 // Update the URL in urlTextBox when the page starts to load.
618 public void onPageStarted(WebView view, String url, Bitmap favicon) {
619 // Check to see if we are waiting on Orbot.
620 if (pendingUrl.isEmpty()) { // We are not waiting on Orbot, so we need to process the URL.
621 // 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.
622 formattedUrlString = url;
624 // Display the loading URL is the URL text box.
625 urlTextBox.setText(url);
627 // Apply any custom domain settings if the URL was loaded by navigating history.
628 if (navigatingHistory) {
629 applyDomainSettings(url);
634 // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load.
636 public void onPageFinished(WebView view, String url) {
637 // Check to see if we are waiting on Orbot.
638 if (pendingUrl.isEmpty()) { // we are not waiting on Orbot, so we need to process the URL.
639 formattedUrlString = url;
641 // Only update urlTextBox if the user is not typing in it.
642 if (!urlTextBox.hasFocus()) {
643 urlTextBox.setText(formattedUrlString);
646 // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog`.
647 sslCertificate = mainWebView.getCertificate();
651 // Handle SSL Certificate errors.
653 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
654 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
655 sslErrorHandler = handler;
657 // Display the SSL error `AlertDialog`.
658 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
659 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
663 mainWebView.setWebChromeClient(new WebChromeClient() {
664 // Update the progress bar when a page is loading.
666 public void onProgressChanged(WebView view, int progress) {
667 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
668 progressBar.setProgress(progress);
669 if (progress < 100) {
670 progressBar.setVisibility(View.VISIBLE);
672 progressBar.setVisibility(View.GONE);
674 //Stop the `SwipeToRefresh` indicator if it is running
675 swipeRefreshLayout.setRefreshing(false);
679 // Set the favorite icon when it changes.
681 public void onReceivedIcon(WebView view, Bitmap icon) {
682 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
685 // Place the favorite icon in the appBar.
686 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
687 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
690 // Enter full screen video
692 public void onShowCustomView(View view, CustomViewCallback callback) {
693 // Pause the ad if this is the free flavor.
694 if (BuildConfig.FLAVOR.contentEquals("free")) {
695 BannerAd.pauseAd(adView);
698 // Remove the translucent overlays.
699 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
701 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
702 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
704 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
705 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
706 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
708 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
710 // Set `rootCoordinatorLayout` to fill the entire screen.
711 rootCoordinatorLayout.setFitsSystemWindows(false);
713 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
714 fullScreenVideoFrameLayout.addView(view);
715 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
718 // Exit full screen video
719 public void onHideCustomView() {
720 // Hide `fullScreenVideoFrameLayout`.
721 fullScreenVideoFrameLayout.removeAllViews();
722 fullScreenVideoFrameLayout.setVisibility(View.GONE);
724 // Add the translucent status flag. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
725 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
727 // Set `rootCoordinatorLayout` to fit inside the status and navigation bars. This also clears the `SYSTEM_UI` flags.
728 rootCoordinatorLayout.setFitsSystemWindows(true);
730 // Show the ad if this is the free flavor.
731 if (BuildConfig.FLAVOR.contentEquals("free")) {
732 // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`.
733 BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
735 // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
736 adView = findViewById(R.id.adView);
741 // Register `mainWebView` for a context menu. This is used to see link targets and download images.
742 registerForContextMenu(mainWebView);
744 // Allow the downloading of files.
745 mainWebView.setDownloadListener(new DownloadListener() {
747 public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
748 // Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`.
749 AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
750 downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
754 // Allow pinch to zoom.
755 mainWebView.getSettings().setBuiltInZoomControls(true);
757 // Hide zoom controls.
758 mainWebView.getSettings().setDisplayZoomControls(false);
760 // Initialize cookieManager.
761 cookieManager = CookieManager.getInstance();
763 // 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).
764 customHeaders.put("X-Requested-With", "");
766 // Initialize the default preference values the first time the program is run. `this` is the context. `false` keeps this command from resetting any current preferences back to default.
767 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
769 // Get the intent that started the app.
770 final Intent launchingIntent = getIntent();
772 // Extract the launching intent data as `launchingIntentUriData`.
773 final Uri launchingIntentUriData = launchingIntent.getData();
775 // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`.
776 if (launchingIntentUriData != null) {
777 formattedUrlString = launchingIntentUriData.toString();
780 // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
781 inFullScreenBrowsingMode = false;
783 // Initialize AdView for the free flavor.
784 adView = findViewById(R.id.adView);
786 // Initialize the privacy settings variables.
787 javaScriptEnabled = false;
788 firstPartyCookiesEnabled = false;
789 thirdPartyCookiesEnabled = false;
790 domStorageEnabled = false;
791 saveFormDataEnabled = false;
793 // Apply the settings from the shared preferences.
796 // Load `formattedUrlString` if we are not proxying through Orbot and waiting for Orbot to connect.
797 if (!(proxyThroughOrbot && !orbotStatus.equals("ON"))) {
798 loadUrl(formattedUrlString);
801 // If the favorite icon is null, load the default.
802 if (favoriteIcon == null) {
803 // We have to use `ContextCompat` until API >= 21.
804 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
805 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
806 favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
812 protected void onNewIntent(Intent intent) {
813 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
816 if (intent.getData() != null) {
817 // Get the intent data and convert it to a string.
818 final Uri intentUriData = intent.getData();
819 formattedUrlString = intentUriData.toString();
822 // Close the navigation drawer if it is open.
823 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
824 drawerLayout.closeDrawer(GravityCompat.START);
828 loadUrl(formattedUrlString);
830 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
831 mainWebView.requestFocus();
835 public boolean onCreateOptionsMenu(Menu menu) {
836 // Inflate the menu; this adds items to the action bar if it is present.
837 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
839 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
842 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
843 updatePrivacyIcons(false);
845 // Get handles for the menu items.
846 MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
847 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
848 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
849 MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
851 // Only display third-party cookies if SDK >= 21
852 toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21);
854 // Get the shared preference values. `this` references the current context.
855 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
857 // Set the status of the additional app bar icons. The default is `false`.
858 if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
859 toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
860 toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
861 toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
862 } else { //Do not display the additional icons.
863 toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
864 toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
865 toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
872 public boolean onPrepareOptionsMenu(Menu menu) {
873 // Get handles for the menu items.
874 MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
875 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
876 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
877 MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
878 MenuItem clearCookies = menu.findItem(R.id.clearCookies);
879 MenuItem clearFormData = menu.findItem(R.id.clearFormData);
880 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
882 // Set the status of the menu item checkboxes.
883 toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
884 toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
885 toggleDomStorage.setChecked(domStorageEnabled);
886 toggleSaveFormData.setChecked(saveFormDataEnabled);
888 // Enable third-party cookies if first-party cookies are enabled.
889 toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled);
891 // Enable DOM Storage if JavaScript is enabled.
892 toggleDomStorage.setEnabled(javaScriptEnabled);
894 // Enable Clear Cookies if there are any.
895 clearCookies.setEnabled(cookieManager.hasCookies());
897 // Enable Clear Form Data is there is any.
898 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
899 clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
901 // Only show `Refresh` if `swipeToRefresh` is disabled.
902 refreshMenuItem.setVisible(!swipeToRefreshEnabled);
904 // Initialize font size variables.
905 int fontSize = mainWebView.getSettings().getTextZoom();
906 String fontSizeTitle;
907 MenuItem selectedFontSizeMenuItem;
909 // Prepare the font size title and current size menu item.
912 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
913 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
917 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
918 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
922 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
923 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
927 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
928 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
932 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
933 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
937 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
938 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
942 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
943 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
947 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
948 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
952 // Set the font size title and select the current size menu item.
953 MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
954 fontSizeMenuItem.setTitle(fontSizeTitle);
955 selectedFontSizeMenuItem.setChecked(true);
957 // Run all the other default commands.
958 super.onPrepareOptionsMenu(menu);
960 // `return true` displays the menu.
965 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
966 @SuppressLint("SetJavaScriptEnabled")
967 // removeAllCookies is deprecated, but it is required for API < 21.
968 @SuppressWarnings("deprecation")
969 public boolean onOptionsItemSelected(MenuItem menuItem) {
970 int menuItemId = menuItem.getItemId();
972 // Set the commands that relate to the menu entries.
973 switch (menuItemId) {
974 case R.id.toggleJavaScript:
975 // Switch the status of javaScriptEnabled.
976 javaScriptEnabled = !javaScriptEnabled;
978 // Apply the new JavaScript status.
979 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
981 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
982 updatePrivacyIcons(true);
984 // Display a `Snackbar`.
985 if (javaScriptEnabled) { // JavaScrip is enabled.
986 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
987 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
988 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
989 } else { // Privacy mode.
990 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
993 // Reload the WebView.
994 mainWebView.reload();
997 case R.id.toggleFirstPartyCookies:
998 // Switch the status of firstPartyCookiesEnabled.
999 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
1001 // Update the menu checkbox.
1002 menuItem.setChecked(firstPartyCookiesEnabled);
1004 // Apply the new cookie status.
1005 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1007 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1008 updatePrivacyIcons(true);
1010 // Display a `Snackbar`.
1011 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
1012 Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1013 } else if (javaScriptEnabled){ // JavaScript is still enabled.
1014 Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1015 } else { // Privacy mode.
1016 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1019 // Reload the WebView.
1020 mainWebView.reload();
1023 case R.id.toggleThirdPartyCookies:
1024 if (Build.VERSION.SDK_INT >= 21) {
1025 // Switch the status of thirdPartyCookiesEnabled.
1026 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
1028 // Update the menu checkbox.
1029 menuItem.setChecked(thirdPartyCookiesEnabled);
1031 // Apply the new cookie status.
1032 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
1034 // Display a `Snackbar`.
1035 if (thirdPartyCookiesEnabled) {
1036 Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1038 Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1041 // Reload the WebView.
1042 mainWebView.reload();
1043 } // Else do nothing because SDK < 21.
1046 case R.id.toggleDomStorage:
1047 // Switch the status of domStorageEnabled.
1048 domStorageEnabled = !domStorageEnabled;
1050 // Update the menu checkbox.
1051 menuItem.setChecked(domStorageEnabled);
1053 // Apply the new DOM Storage status.
1054 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
1056 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1057 updatePrivacyIcons(true);
1059 // Display a `Snackbar`.
1060 if (domStorageEnabled) {
1061 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1063 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1066 // Reload the WebView.
1067 mainWebView.reload();
1070 case R.id.toggleSaveFormData:
1071 // Switch the status of saveFormDataEnabled.
1072 saveFormDataEnabled = !saveFormDataEnabled;
1074 // Update the menu checkbox.
1075 menuItem.setChecked(saveFormDataEnabled);
1077 // Apply the new form data status.
1078 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
1080 // Display a `Snackbar`.
1081 if (saveFormDataEnabled) {
1082 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1084 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1087 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1088 updatePrivacyIcons(true);
1090 // Reload the WebView.
1091 mainWebView.reload();
1094 case R.id.clearCookies:
1095 if (Build.VERSION.SDK_INT < 21) {
1096 cookieManager.removeAllCookie();
1098 cookieManager.removeAllCookies(null);
1100 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
1103 case R.id.clearDomStorage:
1104 WebStorage webStorage = WebStorage.getInstance();
1105 webStorage.deleteAllData();
1106 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
1109 case R.id.clearFormData:
1110 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
1111 mainWebViewDatabase.clearFormData();
1112 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_deleted, Snackbar.LENGTH_SHORT).show();
1115 case R.id.fontSizeFiftyPercent:
1116 mainWebView.getSettings().setTextZoom(50);
1119 case R.id.fontSizeSeventyFivePercent:
1120 mainWebView.getSettings().setTextZoom(75);
1123 case R.id.fontSizeOneHundredPercent:
1124 mainWebView.getSettings().setTextZoom(100);
1127 case R.id.fontSizeOneHundredTwentyFivePercent:
1128 mainWebView.getSettings().setTextZoom(125);
1131 case R.id.fontSizeOneHundredFiftyPercent:
1132 mainWebView.getSettings().setTextZoom(150);
1135 case R.id.fontSizeOneHundredSeventyFivePercent:
1136 mainWebView.getSettings().setTextZoom(175);
1139 case R.id.fontSizeTwoHundredPercent:
1140 mainWebView.getSettings().setTextZoom(200);
1143 case R.id.find_on_page:
1144 // Hide the URL app bar.
1145 supportAppBar.setVisibility(View.GONE);
1147 // Show the Find on Page `RelativeLayout`.
1148 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1150 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
1151 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1152 findOnPageEditText.postDelayed(new Runnable()
1157 // Set the focus on `findOnPageEditText`.
1158 findOnPageEditText.requestFocus();
1160 // Display the keyboard.
1161 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1167 Intent shareIntent = new Intent();
1168 shareIntent.setAction(Intent.ACTION_SEND);
1169 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
1170 shareIntent.setType("text/plain");
1171 startActivity(Intent.createChooser(shareIntent, "Share URL"));
1174 case R.id.addToHomescreen:
1175 // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
1176 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
1177 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut));
1179 //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below.
1183 // Get a `PrintManager` instance.
1184 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1186 // Convert `mainWebView` to `printDocumentAdapter`.
1187 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
1189 // Print the document. The print attributes are `null`.
1190 printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
1194 mainWebView.reload();
1198 // Don't consume the event.
1199 return super.onOptionsItemSelected(menuItem);
1203 // removeAllCookies is deprecated, but it is required for API < 21.
1204 @SuppressWarnings("deprecation")
1206 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1207 int menuItemId = menuItem.getItemId();
1209 switch (menuItemId) {
1215 if (mainWebView.canGoBack()) {
1216 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
1217 navigatingHistory = true;
1219 // Load the previous website in the history.
1220 mainWebView.goBack();
1225 if (mainWebView.canGoForward()) {
1226 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
1227 navigatingHistory = true;
1229 // Load the next website in the history.
1230 mainWebView.goForward();
1235 // Gte the `WebBackForwardList`.
1236 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
1238 // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`.
1239 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
1240 urlHistoryDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.history));
1243 case R.id.bookmarks:
1244 // Launch BookmarksActivity.
1245 Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
1246 startActivity(bookmarksIntent);
1249 case R.id.downloads:
1250 // Launch the system Download Manager.
1251 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
1253 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
1254 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1256 startActivity(downloadManagerIntent);
1260 // Launch `SettingsActivity`.
1261 Intent settingsIntent = new Intent(this, SettingsActivity.class);
1262 startActivity(settingsIntent);
1266 // Launch `DomainsActivity`.
1267 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1268 startActivity(domainsIntent);
1272 // Launch `GuideActivity`.
1273 Intent guideIntent = new Intent(this, GuideActivity.class);
1274 startActivity(guideIntent);
1278 // Launch `AboutActivity`.
1279 Intent aboutIntent = new Intent(this, AboutActivity.class);
1280 startActivity(aboutIntent);
1283 case R.id.clearAndExit:
1284 // Clear cookies. The commands changed slightly in API 21.
1285 if (Build.VERSION.SDK_INT >= 21) {
1286 cookieManager.removeAllCookies(null);
1288 cookieManager.removeAllCookie();
1291 // Clear DOM storage.
1292 WebStorage domStorage = WebStorage.getInstance();
1293 domStorage.deleteAllData();
1296 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1297 webViewDatabase.clearFormData();
1299 // Clear cache. The argument of "true" includes disk files.
1300 mainWebView.clearCache(true);
1302 // Clear the back/forward history.
1303 mainWebView.clearHistory();
1305 // Clear any SSL certificate preferences.
1306 mainWebView.clearSslPreferences();
1308 // Clear `formattedUrlString`.
1309 formattedUrlString = null;
1311 // Clear `customHeaders`.
1312 customHeaders.clear();
1314 // Detach all views from `mainWebViewRelativeLayout`.
1315 mainWebViewRelativeLayout.removeAllViews();
1317 // Destroy the internal state of `mainWebView`.
1318 mainWebView.destroy();
1320 // Manually delete the `app_webview` folder, which contains an additional `WebView` cache. See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
1321 Runtime runtime = Runtime.getRuntime();
1322 String dataDirString = getApplicationInfo().dataDir; // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1324 runtime.exec("rm -rf " + dataDirString + "/app_webview");
1325 } catch (IOException e) {
1326 // Do nothing if the files do not exist.
1329 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
1330 if (Build.VERSION.SDK_INT >= 21) {
1331 finishAndRemoveTask();
1336 // Remove the terminated program from RAM. The status code is `0`.
1344 // Close the navigation drawer.
1345 drawerLayout.closeDrawer(GravityCompat.START);
1350 public void onPostCreate(Bundle savedInstanceState) {
1351 super.onPostCreate(savedInstanceState);
1353 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
1354 drawerToggle.syncState();
1358 public void onConfigurationChanged(Configuration newConfig) {
1359 super.onConfigurationChanged(newConfig);
1361 // Reload the ad for the free flavor if we are not in full screen mode.
1362 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
1364 BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
1366 // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
1367 adView = findViewById(R.id.adView);
1370 // `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
1371 // ActivityCompat.invalidateOptionsMenu(this);
1375 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
1376 // Store the `HitTestResult`.
1377 final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
1380 final String imageUrl;
1381 final String linkUrl;
1383 // Get a handle for the `ClipboardManager`.
1384 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1386 switch (hitTestResult.getType()) {
1387 // `SRC_ANCHOR_TYPE` is a link.
1388 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1389 // Get the target URL.
1390 linkUrl = hitTestResult.getExtra();
1392 // Set the target URL as the title of the `ContextMenu`.
1393 menu.setHeaderTitle(linkUrl);
1395 // Add a `Load URL` entry.
1396 menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1398 public boolean onMenuItemClick(MenuItem item) {
1404 // Add a `Copy URL` entry.
1405 menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1407 public boolean onMenuItemClick(MenuItem item) {
1408 // Save the link URL in a `ClipData`.
1409 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), linkUrl);
1411 // Set the `ClipData` as the clipboard's primary clip.
1412 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
1417 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1418 menu.add(R.string.cancel);
1421 case WebView.HitTestResult.EMAIL_TYPE:
1422 // Get the target URL.
1423 linkUrl = hitTestResult.getExtra();
1425 // Set the target URL as the title of the `ContextMenu`.
1426 menu.setHeaderTitle(linkUrl);
1428 // Add a `Write Email` entry.
1429 menu.add(R.string.write_email).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1431 public boolean onMenuItemClick(MenuItem item) {
1432 // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1433 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1435 // Parse the url and set it as the data for the `Intent`.
1436 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
1438 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
1439 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1442 startActivity(emailIntent);
1447 // Add a `Copy Email Address` entry.
1448 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1450 public boolean onMenuItemClick(MenuItem item) {
1451 // Save the email address in a `ClipData`.
1452 ClipData srcEmailTypeClipData = ClipData.newPlainText(getResources().getString(R.string.email_address), linkUrl);
1454 // Set the `ClipData` as the clipboard's primary clip.
1455 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
1460 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1461 menu.add(R.string.cancel);
1464 // `IMAGE_TYPE` is an image.
1465 case WebView.HitTestResult.IMAGE_TYPE:
1466 // Get the image URL.
1467 imageUrl = hitTestResult.getExtra();
1469 // Set the image URL as the title of the `ContextMenu`.
1470 menu.setHeaderTitle(imageUrl);
1472 // Add a `View Image` entry.
1473 menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1475 public boolean onMenuItemClick(MenuItem item) {
1481 // Add a `Download Image` entry.
1482 menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1484 public boolean onMenuItemClick(MenuItem item) {
1485 // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
1486 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
1487 downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
1492 // Add a `Copy URL` entry.
1493 menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1495 public boolean onMenuItemClick(MenuItem item) {
1496 // Save the image URL in a `ClipData`.
1497 ClipData srcImageTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl);
1499 // Set the `ClipData` as the clipboard's primary clip.
1500 clipboardManager.setPrimaryClip(srcImageTypeClipData);
1505 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1506 menu.add(R.string.cancel);
1510 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
1511 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1512 // Get the image URL.
1513 imageUrl = hitTestResult.getExtra();
1515 // Set the image URL as the title of the `ContextMenu`.
1516 menu.setHeaderTitle(imageUrl);
1518 // Add a `View Image` entry.
1519 menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1521 public boolean onMenuItemClick(MenuItem item) {
1527 // Add a `Download Image` entry.
1528 menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1530 public boolean onMenuItemClick(MenuItem item) {
1531 // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
1532 AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
1533 downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
1538 // Add a `Copy URL` entry.
1539 menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1541 public boolean onMenuItemClick(MenuItem item) {
1542 // Save the image URL in a `ClipData`.
1543 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl);
1545 // Set the `ClipData` as the clipboard's primary clip.
1546 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
1551 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1552 menu.add(R.string.cancel);
1558 public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
1559 // Get shortcutNameEditText from the alert dialog.
1560 EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
1562 // Create the bookmark shortcut based on formattedUrlString.
1563 Intent bookmarkShortcut = new Intent();
1564 bookmarkShortcut.setAction(Intent.ACTION_VIEW);
1565 bookmarkShortcut.setData(Uri.parse(formattedUrlString));
1567 // Place the bookmark shortcut on the home screen.
1568 Intent placeBookmarkShortcut = new Intent();
1569 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
1570 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
1571 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
1572 placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1573 sendBroadcast(placeBookmarkShortcut);
1577 public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
1578 // Download the image if it has an HTTP or HTTPS URI.
1579 if (imageUrl.startsWith("http")) {
1580 // Get a handle for the system `DOWNLOAD_SERVICE`.
1581 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
1583 // Parse `imageUrl`.
1584 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
1586 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
1587 if (firstPartyCookiesEnabled) {
1588 // Get the cookies for `imageUrl`.
1589 String cookies = cookieManager.getCookie(imageUrl);
1591 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
1592 downloadRequest.addRequestHeader("Cookie", cookies);
1595 // Get the file name from `dialogFragment`.
1596 EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name);
1597 String imageName = downloadImageNameEditText.getText().toString();
1599 // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
1600 if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`.
1601 downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName);
1602 } else { // Only set the title using `imageName`.
1603 downloadRequest.setTitle(imageName);
1606 // Allow `MediaScanner` to index the download if it is a media file.
1607 downloadRequest.allowScanningByMediaScanner();
1609 // Add the URL as the description for the download.
1610 downloadRequest.setDescription(imageUrl);
1612 // Show the download notification after the download is completed.
1613 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1615 // Initiate the download.
1616 downloadManager.enqueue(downloadRequest);
1617 } else { // The image is not an HTTP or HTTPS URI.
1618 Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
1623 public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
1624 // Download the file if it has an HTTP or HTTPS URI.
1625 if (downloadUrl.startsWith("http")) {
1627 // Get a handle for the system `DOWNLOAD_SERVICE`.
1628 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
1630 // Parse `downloadUrl`.
1631 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
1633 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
1634 if (firstPartyCookiesEnabled) {
1635 // Get the cookies for `downloadUrl`.
1636 String cookies = cookieManager.getCookie(downloadUrl);
1638 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
1639 downloadRequest.addRequestHeader("Cookie", cookies);
1642 // Get the file name from `dialogFragment`.
1643 EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
1644 String fileName = downloadFileNameEditText.getText().toString();
1646 // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
1647 if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download location to `/sdcard/Android/data/com.stoutner.privacybrowser.standard/files` named `fileName`.
1648 downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
1649 } else { // Only set the title using `fileName`.
1650 downloadRequest.setTitle(fileName);
1653 // Allow `MediaScanner` to index the download if it is a media file.
1654 downloadRequest.allowScanningByMediaScanner();
1656 // Add the URL as the description for the download.
1657 downloadRequest.setDescription(downloadUrl);
1659 // Show the download notification after the download is completed.
1660 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1662 // Initiate the download.
1663 downloadManager.enqueue(downloadRequest);
1664 } else { // The download is not an HTTP or HTTPS URI.
1665 Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
1669 public void viewSslCertificate(View view) {
1670 // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
1671 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
1672 viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
1676 public void onSslErrorCancel() {
1677 sslErrorHandler.cancel();
1681 public void onSslErrorProceed() {
1682 sslErrorHandler.proceed();
1686 public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
1687 // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
1688 navigatingHistory = true;
1690 // Load the history entry.
1691 mainWebView.goBackOrForward(moveBackOrForwardSteps);
1695 public void onClearHistory() {
1696 // Clear the history.
1697 mainWebView.clearHistory();
1700 // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
1702 public void onBackPressed() {
1703 // Close the navigation drawer if it is available. GravityCompat.START is the drawer on the left on Left-to-Right layout text.
1704 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1705 drawerLayout.closeDrawer(GravityCompat.START);
1707 // Load the previous URL if available.
1708 if (mainWebView.canGoBack()) {
1709 mainWebView.goBack();
1711 // Pass `onBackPressed()` to the system.
1712 super.onBackPressed();
1718 public void onPause() {
1719 // Pause `mainWebView`.
1720 mainWebView.onPause();
1722 // Stop all JavaScript.
1723 mainWebView.pauseTimers();
1725 // Pause the adView or it will continue to consume resources in the background on the free flavor.
1726 if (BuildConfig.FLAVOR.contentEquals("free")) {
1727 BannerAd.pauseAd(adView);
1734 public void onResume() {
1737 // Resume JavaScript (if enabled).
1738 mainWebView.resumeTimers();
1740 // Resume `mainWebView`.
1741 mainWebView.onResume();
1743 // Resume the adView for the free flavor.
1744 if (BuildConfig.FLAVOR.contentEquals("free")) {
1745 BannerAd.resumeAd(adView);
1750 public void onRestart() {
1753 // Apply the settings from shared preferences, which might have been changed in `SettingsActivity`.
1756 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1757 updatePrivacyIcons(true);
1761 private void loadUrlFromTextBox() throws UnsupportedEncodingException {
1762 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
1763 String unformattedUrlString = urlTextBox.getText().toString().trim();
1765 URL unformattedUrl = null;
1766 Uri.Builder formattedUri = new Uri.Builder();
1768 // Check to see if unformattedUrlString is a valid URL. Otherwise, convert it into a Duck Duck Go search.
1769 if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
1770 // Add http:// at the beginning if it is missing. Otherwise the app will segfault.
1771 if (!unformattedUrlString.startsWith("http")) {
1772 unformattedUrlString = "http://" + unformattedUrlString;
1775 // 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.
1777 unformattedUrl = new URL(unformattedUrlString);
1778 } catch (MalformedURLException e) {
1779 e.printStackTrace();
1782 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1783 final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1784 final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1785 final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1786 final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1787 final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1789 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1790 formattedUrlString = formattedUri.build().toString();
1792 // Sanitize the search input and convert it to a DuckDuckGo search.
1793 final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1795 // Use the correct search URL.
1796 if (javaScriptEnabled) { // JavaScript is enabled.
1797 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1798 } else { // JavaScript is disabled.
1799 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1803 loadUrl(formattedUrlString);
1805 // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
1806 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1810 private void loadUrl(String url) {
1811 // Apply any custom domain settings.
1812 applyDomainSettings(url);
1815 mainWebView.loadUrl(url, customHeaders);
1818 // We have to use the deprecated `.getColor()` until the minimum API >= 23.
1819 @SuppressWarnings("deprecation")
1820 private void applyDomainSettings(String url) {
1821 // Parse the URL into a URI.
1822 Uri uri = Uri.parse(url);
1824 // Extract the domain from `uri`.
1825 String hostname = uri.getHost();
1827 // Initialize the database handler. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
1828 // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1829 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1831 // Get a full cursor from `domainsDatabaseHelper`.
1832 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
1834 // Initialize `domainSettingsSet`.
1835 Set<String> domainSettingsSet = new HashSet<>();
1837 // Get the domain name column index.
1838 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
1840 // Populate `domainSettingsSet`.
1841 for (int i=0; i<domainNameCursor.getCount(); i++) {
1842 // Move `domainsCursor` to the current row.
1843 domainNameCursor.moveToPosition(i);
1845 // Store the domain name in `domainSettingsSet`.
1846 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
1849 // Close `domainNameCursor.
1850 domainNameCursor.close();
1852 // Initialize variables to track if this domain has stored domain settings, and if so, under which name.
1853 boolean hostHasDomainSettings = false;
1854 String domainNameInDatabase = null;
1856 // Check all the subdomains of `domain` if it is not `null` against the list of domains in `domainCursor`.
1857 if (hostname != null) {
1858 while (hostname.contains(".") && !hostHasDomainSettings) { // Stop checking if we run out of `.` or if we already know that `hostHasDomainSettings` is `true`.
1859 if (domainSettingsSet.contains(hostname)) { // Check the host name.
1860 hostHasDomainSettings = true;
1861 domainNameInDatabase = hostname;
1862 } else if (domainSettingsSet.contains("*." + hostname)){ // Check the host name prepended by `*.`.
1863 hostHasDomainSettings = true;
1864 domainNameInDatabase = "*." + hostname;
1867 // Strip out the lowest subdomain of `host`.
1868 hostname = hostname.substring(hostname.indexOf(".") + 1);
1872 FrameLayout urlAppBarFrameLayout = (FrameLayout) findViewById(R.id.url_app_bar_framelayout);
1874 if (hostHasDomainSettings) { // The url we are loading has custom domain settings.
1875 // Get a cursor for the current host and move it to the first position.
1876 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
1877 currentHostDomainSettingsCursor.moveToFirst();
1879 // Get the settings from the cursor.
1880 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
1881 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
1882 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
1883 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
1884 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
1885 String userAgentString = (currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)));
1886 int fontSize = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)));
1888 // Close `currentHostDomainSettingsCursor`.
1889 currentHostDomainSettingsCursor.close();
1891 // Apply the domain settings.
1892 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
1893 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1894 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
1895 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
1896 mainWebView.getSettings().setTextZoom(fontSize);
1898 // Set third-party cookies status if API >= 21.
1899 if (Build.VERSION.SDK_INT >= 21) {
1900 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
1903 // Set the user agent.
1904 if (userAgentString.equals("WebView default user agent")) {
1905 // Set the user agent to `""`, which uses the default value.
1906 mainWebView.getSettings().setUserAgentString("");
1908 // Use the selected user agent.
1909 mainWebView.getSettings().setUserAgentString(userAgentString);
1912 // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. We have to use the deprecated `.getColor()` until the minimum API >= 23.
1913 urlAppBarFrameLayout.setBackgroundColor(getResources().getColor(R.color.green_100));
1914 } else { // The URL we are loading does not have custom domain settings. Load the defaults.
1915 // Get the shared preference values. `this` references the current context.
1916 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1918 // Store the values from `sharedPreferences` in variables.
1919 javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
1920 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
1921 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
1922 domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
1923 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
1924 String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
1925 String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
1926 String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
1928 // Apply the default settings.
1929 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
1930 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1931 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
1932 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
1933 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
1935 // Set third-party cookies status if API >= 21.
1936 if (Build.VERSION.SDK_INT >= 21) {
1937 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
1940 // Set the default user agent.
1941 switch (userAgentString) {
1942 case "WebView default user agent":
1943 // Set the user agent to `""`, which uses the default value.
1944 mainWebView.getSettings().setUserAgentString("");
1947 case "Custom user agent":
1948 // Set the custom user agent.
1949 mainWebView.getSettings().setUserAgentString(customUserAgentString);
1953 // Use the selected user agent.
1954 mainWebView.getSettings().setUserAgentString(userAgentString);
1958 // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getColor()` until the minimum API >= 23.
1959 urlAppBarFrameLayout.setBackgroundColor(getResources().getColor(R.color.transparent));
1962 // Close `domainsDatabaseHelper`.
1963 domainsDatabaseHelper.close();
1965 // Update the privacy icons, but only if `mainMenu` has already been populated.
1966 if (mainMenu != null) {
1967 updatePrivacyIcons(true);
1971 public void findPreviousOnPage(View view) {
1972 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
1973 mainWebView.findNext(false);
1976 public void findNextOnPage(View view) {
1977 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
1978 mainWebView.findNext(true);
1981 public void closeFindOnPage(View view) {
1982 // Delete the contents of `find_on_page_edittext`.
1983 findOnPageEditText.setText(null);
1985 // Clear the highlighted phrases.
1986 mainWebView.clearMatches();
1988 // Hide the Find on Page `RelativeLayout`.
1989 findOnPageLinearLayout.setVisibility(View.GONE);
1991 // Show the URL app bar.
1992 supportAppBar.setVisibility(View.VISIBLE);
1994 // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
1995 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1998 private void applySettings() {
1999 // Get the shared preference values. `this` references the current context.
2000 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2002 // Store the values from `sharedPreferences` in variables.
2003 String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
2004 String javaScriptDisabledSearchCustomURLString = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
2005 String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
2006 String javaScriptEnabledSearchCustomURLString = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
2007 String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
2008 String torHomepageString = sharedPreferences.getString("tor_homepage", "https://3g2upl4pq6kufc4m.onion");
2009 String torJavaScriptDisabledSearchString = sharedPreferences.getString("tor_javascript_disabled_search", "https://3g2upl4pq6kufc4m.onion/html/?q=");
2010 String torJavaScriptDisabledSearchCustomURLString = sharedPreferences.getString("tor_javascript_disabled_search_custom_url", "");
2011 String torJavaScriptEnabledSearchString = sharedPreferences.getString("tor_javascript_enabled_search", "https://3g2upl4pq6kufc4m.onion/?q=");
2012 String torJavaScriptEnabledSearchCustomURLString = sharedPreferences.getString("tor_javascript_enabled_search_custom_url", "");
2013 swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false);
2014 adBlockerEnabled = sharedPreferences.getBoolean("block_ads", true);
2015 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
2016 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
2017 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("enable_full_screen_browsing_mode", false);
2018 hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
2019 translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
2021 // Set the homepage, search, and proxy options.
2022 if (proxyThroughOrbot) { // Set the Tor options.
2023 // Set `torHomepageString` as `homepage`.
2024 homepage = torHomepageString;
2026 // If formattedUrlString is null assign the homepage to it.
2027 if (formattedUrlString == null) {
2028 formattedUrlString = homepage;
2031 // Set JavaScript disabled search.
2032 if (torJavaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string.
2033 javaScriptDisabledSearchURL = torJavaScriptDisabledSearchCustomURLString;
2034 } else { // Use the string from the pre-built list.
2035 javaScriptDisabledSearchURL = torJavaScriptDisabledSearchString;
2038 // Set JavaScript enabled search.
2039 if (torJavaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string.
2040 javaScriptEnabledSearchURL = torJavaScriptEnabledSearchCustomURLString;
2041 } else { // Use the string from the pre-built list.
2042 javaScriptEnabledSearchURL = torJavaScriptEnabledSearchString;
2045 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
2046 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
2048 // Display a message to the user if we are waiting on Orbot.
2049 if (!orbotStatus.equals("ON")) {
2050 // Save `formattedUrlString` in `pendingUrl`.
2051 pendingUrl = formattedUrlString;
2053 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
2054 mainWebView.loadData(waitingForOrbotHTMLString, "text/html", null);
2056 } else { // Set the non-Tor options.
2057 // Set `homepageString` as `homepage`.
2058 homepage = homepageString;
2060 // If formattedUrlString is null assign the homepage to it.
2061 if (formattedUrlString == null) {
2062 formattedUrlString = homepage;
2065 // Set JavaScript disabled search.
2066 if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string.
2067 javaScriptDisabledSearchURL = javaScriptDisabledSearchCustomURLString;
2068 } else { // Use the string from the pre-built list.
2069 javaScriptDisabledSearchURL = javaScriptDisabledSearchString;
2072 // Set JavaScript enabled search.
2073 if (javaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string.
2074 javaScriptEnabledSearchURL = javaScriptEnabledSearchCustomURLString;
2075 } else { // Use the string from the pre-built list.
2076 javaScriptEnabledSearchURL = javaScriptEnabledSearchString;
2079 // Reset the proxy to default. The host is `""` and the port is `"0"`.
2080 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
2082 // Reset `pendingUrl` if we are currently waiting for Orbot to connect.
2083 if (!pendingUrl.isEmpty()) {
2084 formattedUrlString = pendingUrl;
2089 // Set swipe to refresh.
2090 swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
2092 // Set Do Not Track status.
2093 if (doNotTrackEnabled) {
2094 customHeaders.put("DNT", "1");
2096 customHeaders.remove("DNT");
2099 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
2100 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {
2101 if (hideSystemBarsOnFullscreen) { // Hide everything.
2102 // Remove the translucent navigation setting if it is currently flagged.
2103 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
2105 // Remove the translucent status bar overlay.
2106 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2108 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
2109 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2111 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2112 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2113 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
2115 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2116 } else { // Hide everything except the status and navigation bars.
2117 // Add the translucent status flag if it is unset.
2118 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2120 if (translucentNavigationBarOnFullscreen) {
2121 // Set the navigation bar to be translucent.
2122 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
2124 // Set the navigation bar to be black.
2125 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
2128 } else { // Switch to normal viewing mode.
2129 // Reset `inFullScreenBrowsingMode` to `false`.
2130 inFullScreenBrowsingMode = false;
2132 // Show the `appBar`.
2135 // Show the `BannerAd` in the free flavor.
2136 if (BuildConfig.FLAVOR.contentEquals("free")) {
2137 // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`.
2138 BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
2140 // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
2141 adView = findViewById(R.id.adView);
2144 // Remove the translucent navigation bar flag if it is set.
2145 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
2147 // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
2148 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2150 // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
2151 rootCoordinatorLayout.setSystemUiVisibility(0);
2153 // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
2154 rootCoordinatorLayout.setFitsSystemWindows(true);
2158 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
2159 // Get handles for the icons.
2160 MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript);
2161 MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies);
2162 MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage);
2163 MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData);
2165 // Update `privacyIcon`.
2166 if (javaScriptEnabled) { // JavaScript is enabled.
2167 privacyIcon.setIcon(R.drawable.javascript_enabled);
2168 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
2169 privacyIcon.setIcon(R.drawable.warning);
2170 } else { // All the dangerous features are disabled.
2171 privacyIcon.setIcon(R.drawable.privacy_mode);
2174 // Update `firstPartyCookiesIcon`.
2175 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
2176 firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled);
2177 } else { // First-party cookies are disabled.
2178 firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
2181 // Update `domStorageIcon`.
2182 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
2183 domStorageIcon.setIcon(R.drawable.dom_storage_enabled);
2184 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
2185 domStorageIcon.setIcon(R.drawable.dom_storage_disabled);
2186 } else { // JavaScript is disabled, so DOM storage is ghosted.
2187 domStorageIcon.setIcon(R.drawable.dom_storage_ghosted);
2190 // Update `formDataIcon`.
2191 if (saveFormDataEnabled) { // Form data is enabled.
2192 formDataIcon.setIcon(R.drawable.form_data_enabled);
2193 } else { // Form data is disabled.
2194 formDataIcon.setIcon(R.drawable.form_data_disabled);
2197 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. `this` references the current activity.
2198 if (runInvalidateOptionsMenu) {
2199 ActivityCompat.invalidateOptionsMenu(this);