+ // Set the favorite icon when it changes.
+ @Override
+ public void onReceivedIcon(WebView view, Bitmap icon) {
+ // Only update the favorite icon if the website has finished loading.
+ if (progressBar.getVisibility() == View.GONE) {
+ // Save a copy of the favorite icon.
+ favoriteIconBitmap = icon;
+
+ // Place the favorite icon in the appBar.
+ favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
+ }
+ }
+
+ // Save a copy of the title when it changes.
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ // Save a copy of the title.
+ webViewTitle = title;
+ }
+
+ // Enter full screen video.
+ @Override
+ public void onShowCustomView(View view, CustomViewCallback callback) {
+ // Pause the ad if this is the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+ AdHelper.pauseAd(findViewById(R.id.adview));
+ }
+
+ // Remove the translucent overlays.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
+ drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+
+ /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+
+ // Set `rootCoordinatorLayout` to fill the entire screen.
+ rootCoordinatorLayout.setFitsSystemWindows(false);
+
+ // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
+ fullScreenVideoFrameLayout.addView(view);
+ fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
+ }
+
+ // Exit full screen video.
+ @Override
+ public void onHideCustomView() {
+ // Hide `fullScreenVideoFrameLayout`.
+ fullScreenVideoFrameLayout.removeAllViews();
+ fullScreenVideoFrameLayout.setVisibility(View.GONE);
+
+ // Add the translucent status flag. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ // Set `rootCoordinatorLayout` to fit inside the status and navigation bars. This also clears the `SYSTEM_UI` flags.
+ rootCoordinatorLayout.setFitsSystemWindows(true);
+
+ // Show the ad if this is the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+ AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id));
+ }
+ }
+
+ // Upload files.
+ @Override
+ public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
+ // Show the file chooser if the device is running API >= 21.
+ if (Build.VERSION.SDK_INT >= 21) {
+ // Store the file path callback.
+ fileChooserCallback = filePathCallback;
+
+ // Create an intent to open a chooser based ont the file chooser parameters.
+ Intent fileChooserIntent = fileChooserParams.createIntent();
+
+ // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
+ startActivityForResult(fileChooserIntent, 0);
+ }
+ return true;
+ }
+ });
+
+ // Register `mainWebView` for a context menu. This is used to see link targets and download images.
+ registerForContextMenu(mainWebView);
+
+ // Allow the downloading of files.
+ mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
+ // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
+ // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
+
+ // Store the variables for future use by `onRequestPermissionsResult()`.
+ downloadUrl = url;
+ downloadContentDisposition = contentDisposition;
+ downloadContentLength = contentLength;
+
+ // Show a dialog if the user has previously denied the permission.
+ if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
+ // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+ DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+
+ // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
+ downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+ } else { // Show the permission request directly.
+ // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+ }
+ } else { // The WRITE_EXTERNAL_STORAGE permission has already been granted.
+ // Get a handle for the download file alert dialog.
+ AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+
+ // Show the download file alert dialog.
+ downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ }
+ });
+
+ // Allow pinch to zoom.
+ mainWebView.getSettings().setBuiltInZoomControls(true);
+
+ // Hide zoom controls.
+ mainWebView.getSettings().setDisplayZoomControls(false);
+
+ // Set `mainWebView` to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
+ mainWebView.getSettings().setUseWideViewPort(true);
+
+ // Set `mainWebView` to load in overview mode (zoomed out to the maximum width).
+ mainWebView.getSettings().setLoadWithOverviewMode(true);
+
+ // Explicitly disable geolocation.
+ mainWebView.getSettings().setGeolocationEnabled(false);
+
+ // Initialize cookieManager.
+ cookieManager = CookieManager.getInstance();
+
+ // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
+ customHeaders.put("X-Requested-With", "");
+
+ // Initialize the default preference values the first time the program is run. `this` is the context. `false` keeps this command from resetting any current preferences back to default.
+ PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+
+ // Get the intent that started the app.
+ final Intent launchingIntent = getIntent();
+
+ // Extract the launching intent data as `launchingIntentUriData`.
+ final Uri launchingIntentUriData = launchingIntent.getData();
+
+ // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`.
+ if (launchingIntentUriData != null) {
+ formattedUrlString = launchingIntentUriData.toString();
+ }
+
+ // Get a handle for the `Runtime`.
+ privacyBrowserRuntime = Runtime.getRuntime();
+
+ // Store the application's private data directory.
+ privateDataDirectoryString = 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`.
+
+ // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
+ inFullScreenBrowsingMode = false;
+
+ // Initialize the privacy settings variables.
+ javaScriptEnabled = false;
+ firstPartyCookiesEnabled = false;
+ thirdPartyCookiesEnabled = false;
+ domStorageEnabled = false;
+ saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
+ nightMode = false;
+
+ // Initialize the WebView title.
+ webViewTitle = getString(R.string.no_title);
+
+ // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21.
+ Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
+ BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
+ assert favoriteIconBitmapDrawable != null;
+ favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
+
+ // If the favorite icon is null, load the default.
+ if (favoriteIconBitmap == null) {
+ favoriteIconBitmap = favoriteIconDefaultBitmap;