X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=02f3742f179468bd309cea8788235046733b433a;hp=c576b89aab6fa192aedc59bdc69173232bec580b;hb=74857298f431569df1849e8ef91387cb25274095;hpb=76a37e5ce26f7d4ebe57d38cecc842e4c6c15819 diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index c576b89a..02f3742f 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2017 Soren Stoutner . + * Copyright © 2015-2017 Soren Stoutner . * * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . * @@ -34,6 +34,7 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -59,7 +60,10 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatDialogFragment; import android.support.v7.widget.Toolbar; import android.text.Editable; +import android.text.Spanned; import android.text.TextWatcher; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; import android.util.Patterns; import android.view.ContextMenu; import android.view.GestureDetector; @@ -118,8 +122,8 @@ import java.util.Set; public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener { - // `appBar` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applyAppSettings()`. - public static ActionBar appBar; + // `darkTheme` is public static so it can be accessed from `AboutActivity` and `GuideActivity`. It is also used in `onCreate()`, `applyAppSettings()`, and `applyDomainSettings()`. + public static boolean darkTheme; // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `ViewSslCertificateDialog`. // It is also used in `onCreate()`, `onCreateHomeScreenShortcutCreate()`, and `applyDomainSettings`. @@ -138,6 +142,15 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`. public static String webViewTitle; + // `displayWebpageImagesBoolean` is public static so it can be accessed from `DomainSettingsFragment`. It is also used in `applyAppSettings()` and `applyDomainSettings()`. + public static boolean displayWebpageImagesBoolean; + + // `reloadOnRestartBoolean` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()` + public static boolean reloadOnRestartBoolean; + + + // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applyAppSettings()`. + private ActionBar appBar; // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`. private boolean navigatingHistory; @@ -151,7 +164,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`. private CoordinatorLayout rootCoordinatorLayout; - // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`. + // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()` + // and `setDisplayWebpageImages()`. private WebView mainWebView; // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`. @@ -201,7 +215,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation private boolean adBlockerEnabled; // `privacyBrowserRuntime` is used in `onCreate()` and `applyAppSettings()`. - Runtime privacyBrowserRuntime; + private Runtime privacyBrowserRuntime; // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`. private boolean incognitoModeEnabled; @@ -218,12 +232,21 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`. private boolean translucentNavigationBarOnFullscreen; - // `currentDomainName` is used in `onCreate(), `onNavigationItemSelected()`, and `applyDomainSettings()`. + // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`. private String currentDomainName; // `waitingForOrbot` is used in `onCreate()` and `applyAppSettings()`. private boolean waitingForOrbot; + // `domainSettingsApplied` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`. + private boolean domainSettingsApplied; + + // `displayWebpageImagesInt` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`. + private int displayWebpageImagesInt; + + // `onTheFlyDisplayImagesSet` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`. + private boolean onTheFlyDisplayImagesSet; + // `waitingForOrbotData` is used in `onCreate()` and `applyAppSettings()`. private String waitingForOrbotHTMLString; @@ -245,9 +268,21 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. private Toolbar supportAppBar; - // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `loadUrl()`. + // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`. private EditText urlTextBox; + // `redColorSpan` is used in `onCreate()` and `highlightUrlText()`. + private ForegroundColorSpan redColorSpan; + + // `initialGrayColorSpan` is sued in `onCreate()` and `highlightUrlText()`. + private ForegroundColorSpan initialGrayColorSpan; + + // `finalGrayColorSpam` is used in `onCreate()` and `highlightUrlText()`. + private ForegroundColorSpan finalGrayColorSpan; + + // `boldStyleSpan` is used in `onCreate()` and `highlightUrlText()`. + private StyleSpan boldStyleSpan; + // `adView` is used in `onCreate()` and `onConfigurationChanged()`. private View adView; @@ -263,8 +298,26 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation @Override // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers. @SuppressLint("SetJavaScriptEnabled") + // Remove Android Studio's warning about deprecations. We have to use the deprecated `getColor()` until API >= 23. + @SuppressWarnings("deprecation") protected void onCreate(Bundle savedInstanceState) { + // Get a handle for `sharedPreferences`. `this` references the current context. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Get the theme preference. + darkTheme = sharedPreferences.getBoolean("dark_theme", false); + + // Set the activity theme. + if (darkTheme) { + setTheme(R.style.PrivacyBrowserDark); + } else { + setTheme(R.style.PrivacyBrowserLight); + } + + // Run the default commands. super.onCreate(savedInstanceState); + + // Set the content view. setContentView(R.layout.main_drawerlayout); // Get a handle for `inputMethodManager`. @@ -282,8 +335,36 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation appBar.setCustomView(R.layout.url_app_bar); appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); - // Set the "go" button on the keyboard to load the URL in urlTextBox. + // Initialize the `ForegroundColorSpans` and `StyleSpan` for highlighting `urlTextBox`. We have to use the deprecated `getColor()` until API >= 23. + redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); + initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); + finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); + boldStyleSpan = new StyleSpan(Typeface.BOLD); + + // Get a handle for `urlTextBox`. urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.url_edittext); + + // Remove the formatting from `urlTextBar` when the user is editing the text. + urlTextBox.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { // The user is editing `urlTextBox`. + // Remove the highlighting. + urlTextBox.getText().removeSpan(redColorSpan); + urlTextBox.getText().removeSpan(initialGrayColorSpan); + urlTextBox.getText().removeSpan(finalGrayColorSpan); + urlTextBox.getText().removeSpan(boldStyleSpan); + } else { // The user has stopped editing `urlTextBox`. + // Reapply the highlighting. + highlightUrlText(); + + // Scroll to the beginning of the text. + urlTextBox.setScrollX(0); + } + } + }); + + // Set the `Go` button on the keyboard to load the URL in `urlTextBox`. urlTextBox.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { @@ -531,8 +612,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation navigationForwardMenuItem.setEnabled(mainWebView.canGoForward()); navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward())); - // Hide the keyboard so we can see the navigation menu. `0` indicates no additional flags. + // Hide the keyboard (if displayed) so we can see the navigation menu. `0` indicates no additional flags. inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + + // Clear the focus from `urlTextBox` if it has it. + urlTextBox.clearFocus(); } } }); @@ -568,8 +652,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - // Use an external email program if the link begins with `mailto:`. - if (url.startsWith("mailto:")) { + if (url.startsWith("mailto:")) { // Load the URL in an external email program because it begins with `mailto:`. // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. Intent emailIntent = new Intent(Intent.ACTION_SENDTO); @@ -581,10 +664,15 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Make it so. startActivity(emailIntent); + + // Returning `true` indicates the application is handling the URL. return true; } else { // Load the URL in Privacy Browser. - loadUrl(url); - return true; + // Apply the domain settings for the new URL. + applyDomainSettings(url); + + // Returning `false` causes the current `WebView` to handle the URL and prevents it from adding redirects to the history list. + return false; } } @@ -636,8 +724,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // 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. formattedUrlString = url; - // Display the loading URL is the URL text box. - urlTextBox.setText(url); + // Display the formatted URL text. + urlTextBox.setText(formattedUrlString); + + // Apply text highlighting to `urlTextBox`. + highlightUrlText(); // Apply any custom domain settings if the URL was loaded by navigating history. if (navigatingHistory) { @@ -663,7 +754,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); // 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`. - privacyBrowserRuntime.exec("rm -rf " + privacyBrowserRuntime + "/app_webview"); + privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); } catch (IOException e) { // Do nothing if an error is thrown. } @@ -676,7 +767,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Set `formattedUrlString` to `""`. formattedUrlString = ""; - // Update `urlTextBox`. urlTextBox.setText(formattedUrlString); // Request focus for `urlTextBox`. @@ -685,7 +775,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Display the keyboard. inputMethodManager.showSoftInput(urlTextBox, 0); - // Apply the domain settings. + // Apply the domain settings. This clears any settings from the previous domain. applyDomainSettings(formattedUrlString); } else { // `WebView` has loaded a webpage. // Set `formattedUrlString`. @@ -693,7 +783,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Only update `urlTextBox` if the user is not typing in it. if (!urlTextBox.hasFocus()) { + // Display the formatted URL text. urlTextBox.setText(formattedUrlString); + + // Apply text highlighting to `urlTextBox`. + highlightUrlText(); } } @@ -822,6 +916,12 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // 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); + // Initialize cookieManager. cookieManager = CookieManager.getInstance(); @@ -883,7 +983,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } - @Override protected void onNewIntent(Intent intent) { // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity. @@ -907,6 +1006,62 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.requestFocus(); } + @Override + public void onRestart() { + super.onRestart(); + + // Apply the app settings, which may have been changed in `SettingsActivity`. + applyAppSettings(); + + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + updatePrivacyIcons(true); + + // Set the display webpage images mode. + setDisplayWebpageImages(); + + // Reload the webpage if displaying of images has been disabled in `SettingsFragment`. + if (reloadOnRestartBoolean) { + // Reload `mainWebView`. + mainWebView.reload(); + + // Reset `reloadOnRestartBoolean`. + reloadOnRestartBoolean = false; + } + } + + // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`. + @Override + public void onResume() { + super.onResume(); + + // Resume JavaScript (if enabled). + mainWebView.resumeTimers(); + + // Resume `mainWebView`. + mainWebView.onResume(); + + // Resume the adView for the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + BannerAd.resumeAd(adView); + } + } + + @Override + public void onPause() { + // Pause `mainWebView`. + mainWebView.onPause(); + + // Stop all JavaScript. + mainWebView.pauseTimers(); + + // Pause the adView or it will continue to consume resources in the background on the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + BannerAd.pauseAd(adView); + } + + super.onPause(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. @@ -919,26 +1074,26 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation updatePrivacyIcons(false); // Get handles for the menu items. - MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies); - MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies); - MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage); - MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData); + MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies); + MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies); + MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage); + MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Only display third-party cookies if SDK >= 21 - toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21); + toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21); // Get the shared preference values. `this` references the current context. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Set the status of the additional app bar icons. The default is `false`. if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) { - toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + toggleSaveFormDataMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } else { //Do not display the additional icons. - toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + toggleSaveFormDataMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } return true; @@ -947,35 +1102,35 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation @Override public boolean onPrepareOptionsMenu(Menu menu) { // Get handles for the menu items. - MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies); - MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies); - MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage); - MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData); - MenuItem clearCookies = menu.findItem(R.id.clearCookies); - MenuItem clearFormData = menu.findItem(R.id.clearFormData); + MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies); + MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies); + MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage); + MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); + MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies); + MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); + MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size); + MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images); MenuItem refreshMenuItem = menu.findItem(R.id.refresh); // Set the status of the menu item checkboxes. - toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled); - toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled); - toggleDomStorage.setChecked(domStorageEnabled); - toggleSaveFormData.setChecked(saveFormDataEnabled); + toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled); + toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled); + toggleDomStorageMenuItem.setChecked(domStorageEnabled); + toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); + displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically()); // Enable third-party cookies if first-party cookies are enabled. - toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled); + toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled); // Enable DOM Storage if JavaScript is enabled. - toggleDomStorage.setEnabled(javaScriptEnabled); + toggleDomStorageMenuItem.setEnabled(javaScriptEnabled); // Enable Clear Cookies if there are any. - clearCookies.setEnabled(cookieManager.hasCookies()); + clearCookiesMenuItem.setEnabled(cookieManager.hasCookies()); // Enable Clear Form Data is there is any. WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); - clearFormData.setEnabled(mainWebViewDatabase.hasFormData()); - - // Only show `Refresh` if `swipeToRefresh` is disabled. - refreshMenuItem.setVisible(!swipeToRefreshEnabled); + clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData()); // Initialize font size variables. int fontSize = mainWebView.getSettings().getTextZoom(); @@ -986,55 +1141,57 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation switch (fontSize) { case 25: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.twenty_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwentyFivePercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent); break; case 50: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent); break; case 75: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent); break; case 100: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent); break; case 125: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent); break; case 150: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent); break; case 175: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent); break; case 200: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent); break; default: fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent); + selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent); break; } // Set the font size title and select the current size menu item. - MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize); fontSizeMenuItem.setTitle(fontSizeTitle); selectedFontSizeMenuItem.setChecked(true); + // Only show `Refresh` if `swipeToRefresh` is disabled. + refreshMenuItem.setVisible(!swipeToRefreshEnabled); + // Run all the other default commands. super.onPrepareOptionsMenu(menu); @@ -1052,7 +1209,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Set the commands that relate to the menu entries. switch (menuItemId) { - case R.id.toggleJavaScript: + case R.id.toggle_javascript: // Switch the status of javaScriptEnabled. javaScriptEnabled = !javaScriptEnabled; @@ -1075,7 +1232,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.reload(); return true; - case R.id.toggleFirstPartyCookies: + case R.id.toggle_first_party_cookies: // Switch the status of firstPartyCookiesEnabled. firstPartyCookiesEnabled = !firstPartyCookiesEnabled; @@ -1101,7 +1258,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.reload(); return true; - case R.id.toggleThirdPartyCookies: + case R.id.toggle_third_party_cookies: if (Build.VERSION.SDK_INT >= 21) { // Switch the status of thirdPartyCookiesEnabled. thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled; @@ -1124,7 +1281,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } // Else do nothing because SDK < 21. return true; - case R.id.toggleDomStorage: + case R.id.toggle_dom_storage: // Switch the status of domStorageEnabled. domStorageEnabled = !domStorageEnabled; @@ -1148,7 +1305,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.reload(); return true; - case R.id.toggleSaveFormData: + case R.id.toggle_save_form_data: // Switch the status of saveFormDataEnabled. saveFormDataEnabled = !saveFormDataEnabled; @@ -1172,64 +1329,154 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.reload(); return true; - case R.id.clearCookies: - if (Build.VERSION.SDK_INT < 21) { - cookieManager.removeAllCookie(); - } else { - cookieManager.removeAllCookies(null); - } - Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show(); + case R.id.clear_cookies: + Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo, new View.OnClickListener() { + @Override + public void onClick(View v) { + // Do nothing because everything will be handled by `onDismissed()` below. + } + }) + .addCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar snackbar, int event) { + switch (event) { + // The user pushed the `Undo` button. + case Snackbar.Callback.DISMISS_EVENT_ACTION: + // Do nothing. + break; + + // The `Snackbar` was dismissed without the `Undo` button being pushed. + default: + // `cookieManager.removeAllCookie()` varies by SDK. + if (Build.VERSION.SDK_INT < 21) { + cookieManager.removeAllCookie(); + } else { + // `null` indicates no callback. + cookieManager.removeAllCookies(null); + } + } + } + }) + .show(); return true; - case R.id.clearDomStorage: - WebStorage webStorage = WebStorage.getInstance(); - webStorage.deleteAllData(); - Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show(); + case R.id.clear_dom_storage: + Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo, new View.OnClickListener() { + @Override + public void onClick(View v) { + // Do nothing because everything will be handled by `onDismissed()` below. + } + }) + .addCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar snackbar, int event) { + switch (event) { + // The user pushed the `Undo` button. + case Snackbar.Callback.DISMISS_EVENT_ACTION: + // Do nothing. + break; + + // The `Snackbar` was dismissed without the `Undo` button being pushed. + default: + // Delete the DOM Storage. + WebStorage webStorage = WebStorage.getInstance(); + webStorage.deleteAllData(); + } + } + }) + .show(); return true; - case R.id.clearFormData: - WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); - mainWebViewDatabase.clearFormData(); - Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_SHORT).show(); + case R.id.clear_form_data: + Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo, new View.OnClickListener() { + @Override + public void onClick(View v) { + // Do nothing because everything will be handled by `onDismissed()` below. + } + }) + .addCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar snackbar, int event) { + switch (event) { + // The user pushed the `Undo` button. + case Snackbar.Callback.DISMISS_EVENT_ACTION: + // Do nothing. + break; + + // The `Snackbar` was dismissed without the `Undo` button being pushed. + default: + // Delete the form data. + WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext()); + mainWebViewDatabase.clearFormData(); + } + } + }) + .show(); return true; - case R.id.fontSizeTwentyFivePercent: + case R.id.font_size_twenty_five_percent: mainWebView.getSettings().setTextZoom(25); return true; - case R.id.fontSizeFiftyPercent: + case R.id.font_size_fifty_percent: mainWebView.getSettings().setTextZoom(50); return true; - case R.id.fontSizeSeventyFivePercent: + case R.id.font_size_seventy_five_percent: mainWebView.getSettings().setTextZoom(75); return true; - case R.id.fontSizeOneHundredPercent: + case R.id.font_size_one_hundred_percent: mainWebView.getSettings().setTextZoom(100); return true; - case R.id.fontSizeOneHundredTwentyFivePercent: + case R.id.font_size_one_hundred_twenty_five_percent: mainWebView.getSettings().setTextZoom(125); return true; - case R.id.fontSizeOneHundredFiftyPercent: + case R.id.font_size_one_hundred_fifty_percent: mainWebView.getSettings().setTextZoom(150); return true; - case R.id.fontSizeOneHundredSeventyFivePercent: + case R.id.font_size_one_hundred_seventy_five_percent: mainWebView.getSettings().setTextZoom(175); return true; - case R.id.fontSizeTwoHundredPercent: + case R.id.font_size_two_hundred_percent: mainWebView.getSettings().setTextZoom(200); return true; + case R.id.display_images: + if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically. + mainWebView.getSettings().setLoadsImagesAutomatically(false); + mainWebView.reload(); + } else { // Images are not currently loaded automatically. + mainWebView.getSettings().setLoadsImagesAutomatically(true); + } + + // Set `onTheFlyDisplayImagesSet`. + onTheFlyDisplayImagesSet = true; + return true; + case R.id.share: + // Setup the share string. + String shareString; + if (webViewTitle != null) { + shareString = webViewTitle + " – " + urlTextBox.getText().toString(); + } else { + shareString = urlTextBox.getText().toString(); + } + + // Create the share intent. Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString()); + shareIntent.putExtra(Intent.EXTRA_TEXT, shareString); shareIntent.setType("text/plain"); + + // Make it so. startActivity(Intent.createChooser(shareIntent, "Share URL")); return true; @@ -1256,10 +1503,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation }, 200); return true; - case R.id.refresh: - mainWebView.reload(); - return true; - case R.id.print: // Get a `PrintManager` instance. PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); @@ -1271,7 +1514,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); return true; - case R.id.addToHomescreen: + case R.id.add_to_homescreen: // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`. AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog(); createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut)); @@ -1279,6 +1522,10 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below. return true; + case R.id.refresh: + mainWebView.reload(); + return true; + default: // Don't consume the event. return super.onOptionsItemSelected(menuItem); @@ -1372,30 +1619,83 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation break; case R.id.clearAndExit: - // Clear cookies. The commands changed slightly in API 21. - if (Build.VERSION.SDK_INT >= 21) { - cookieManager.removeAllCookies(null); - } else { - cookieManager.removeAllCookie(); + // Get a handle for `sharedPreferences`. `this` references the current context. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true); + + // Clear cookies. + if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) { + // The command to remove cookies changed slightly in API 21. + if (Build.VERSION.SDK_INT >= 21) { + cookieManager.removeAllCookies(null); + } else { + cookieManager.removeAllCookie(); + } + + // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run. + try { + // We have to use two commands because `Runtime.exec()` does not like `*`. + privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies"); + privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal"); + } catch (IOException e) { + // Do nothing if an error is thrown. + } } // Clear DOM storage. - WebStorage domStorage = WebStorage.getInstance(); - domStorage.deleteAllData(); + if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) { + // Ask `WebStorage` to clear the DOM storage. + WebStorage webStorage = WebStorage.getInstance(); + webStorage.deleteAllData(); + + // Manually delete the DOM storage directory, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run. + try { + // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. + privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); + } catch (IOException e) { + // Do nothing if an error is thrown. + } + } // Clear form data. - WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); - webViewDatabase.clearFormData(); + if (clearEverything || sharedPreferences.getBoolean("clear_form_data", true)) { + WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); + webViewDatabase.clearFormData(); - // Clear the cache. `true` includes disk files. - mainWebView.clearCache(true); + // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run. + try { + // We have to use a `String[]` because the database contains a space and `Runtime.exec` will not escape the string correctly otherwise. + privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"}); + privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"}); + } catch (IOException e) { + // Do nothing if an error is thrown. + } + } - // Clear the back/forward history. - mainWebView.clearHistory(); + // Clear the cache. + if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) { + // `true` includes disk files. + mainWebView.clearCache(true); + + // Manually delete the cache directories. + try { + // Delete the main cache directory. + privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); + + // Delete the secondary `Service Worker` cache directory. We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. + privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); + } catch (IOException e) { + // Do nothing if an error is thrown. + } + } - // Clear any SSL certificate preferences. + // Clear SSL certificate preferences. mainWebView.clearSslPreferences(); + // Clear the back/forward history. + mainWebView.clearHistory(); + // Clear `formattedUrlString`. formattedUrlString = null; @@ -1408,15 +1708,14 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Destroy the internal state of `mainWebView`. mainWebView.destroy(); - // Manually delete cache folders. - try { - // Delete the main `cache` folder. - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); - - // 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`. - privacyBrowserRuntime.exec("rm -rf " + privacyBrowserRuntime + "/app_webview"); - } catch (IOException e) { - // Do nothing if an error is thrown. + // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache. + // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. + if (clearEverything) { + try { + privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); + } catch (IOException e) { + // Do nothing if an error is thrown. + } } // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list. @@ -1429,9 +1728,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Remove the terminated program from RAM. The status code is `0`. System.exit(0); break; - - default: - break; } // Close the navigation drawer. @@ -1677,6 +1973,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl)); // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login. + // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . if (firstPartyCookiesEnabled) { // Get the cookies for `imageUrl`. String cookies = cookieManager.getCookie(imageUrl); @@ -1724,6 +2021,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl)); // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login. + // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . if (firstPartyCookiesEnabled) { // Get the cookies for `downloadUrl`. String cookies = cookieManager.getCookie(downloadUrl); @@ -1811,56 +2109,12 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } - @Override - public void onPause() { - // Pause `mainWebView`. - mainWebView.onPause(); - - // Stop all JavaScript. - mainWebView.pauseTimers(); - - // Pause the adView or it will continue to consume resources in the background on the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.pauseAd(adView); - } - - super.onPause(); - } - - @Override - public void onResume() { // `onResume()` also runs every time the app starts after `onCreate()` and `onStart()`. - super.onResume(); - - // Resume JavaScript (if enabled). - mainWebView.resumeTimers(); - - // Resume `mainWebView`. - mainWebView.onResume(); - - // Resume the adView for the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.resumeAd(adView); - } - } - - @Override - public void onRestart() { - super.onRestart(); - - // Apply the settings from shared preferences, which might have been changed in `SettingsActivity`. - applyAppSettings(); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - } - private void loadUrlFromTextBox() throws UnsupportedEncodingException { // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string. String unformattedUrlString = urlTextBox.getText().toString().trim(); // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search. - if ((Patterns.WEB_URL.matcher(unformattedUrlString).matches()) || (unformattedUrlString.contains("localhost"))) { + if ((Patterns.WEB_URL.matcher(unformattedUrlString).matches()) || (unformattedUrlString.startsWith("http://")) || (unformattedUrlString.startsWith("https://"))) { // Add `http://` at the beginning if it is missing. Otherwise the app will segfault. if (!unformattedUrlString.startsWith("http")) { unformattedUrlString = "http://" + unformattedUrlString; @@ -1912,218 +2166,39 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.loadUrl(url, customHeaders); } - // We have to use the deprecated `.getDrawable()` until the minimum API >= 21. - @SuppressWarnings("deprecation") - private void applyDomainSettings(String url) { - // Reset `navigatingHistory`. - navigatingHistory = false; - - // Parse the URL into a URI. - Uri uri = Uri.parse(url); - - // Extract the domain from `uri`. - String hostName = uri.getHost(); + public void findPreviousOnPage(View view) { + // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. + mainWebView.findNext(false); + } - // Initialize `loadingNewDomainName`. - boolean loadingNewDomainName; + public void findNextOnPage(View view) { + // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. + mainWebView.findNext(true); + } - // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name. - // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.` - //noinspection SimplifiableIfStatement - if ((hostName == null) || (currentDomainName == null)) { - loadingNewDomainName = true; - } else { // Determine if `hostName` equals `currentDomainName`. - loadingNewDomainName = !hostName.equals(currentDomainName); - } + public void closeFindOnPage(View view) { + // Delete the contents of `find_on_page_edittext`. + findOnPageEditText.setText(null); - // Only apply the domain settings if we are loading a new domain. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc. - if (loadingNewDomainName) { - // Set the new `hostname` as the `currentDomainName`. - currentDomainName = hostName; + // Clear the highlighted phrases. + mainWebView.clearMatches(); - // Reset `favoriteIconBitmap` and display it in the `appbar`. - favoriteIconBitmap = favoriteIconDefaultBitmap; - favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true)); + // Hide the Find on Page `RelativeLayout`. + findOnPageLinearLayout.setVisibility(View.GONE); - // Initialize the database handler. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`. - // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. - DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); + // Show the URL app bar. + supportAppBar.setVisibility(View.VISIBLE); - // Get a full cursor from `domainsDatabaseHelper`. - Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain(); + // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + } - // Initialize `domainSettingsSet`. - Set domainSettingsSet = new HashSet<>(); - - // Get the domain name column index. - int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME); - - // Populate `domainSettingsSet`. - for (int i = 0; i < domainNameCursor.getCount(); i++) { - // Move `domainsCursor` to the current row. - domainNameCursor.moveToPosition(i); - - // Store the domain name in `domainSettingsSet`. - domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex)); - } - - // Close `domainNameCursor. - domainNameCursor.close(); - - // Initialize variables to track if this domain has stored domain settings, and if so, under which name. - boolean hostHasDomainSettings = false; - String domainNameInDatabase = null; - - // Check the hostname. - if (domainSettingsSet.contains(hostName)) { - hostHasDomainSettings = true; - domainNameInDatabase = hostName; - } - - // If `hostName` is not `null`, check all the subdomains of `hostName` against wildcard domains in `domainCursor`. - if (hostName != null) { - while (hostName.contains(".") && !hostHasDomainSettings) { // Stop checking if we run out of `.` or if we already know that `hostHasDomainSettings` is `true`. - if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`. - hostHasDomainSettings = true; - domainNameInDatabase = "*." + hostName; - } - - // Strip out the lowest subdomain of `host`. - hostName = hostName.substring(hostName.indexOf(".") + 1); - } - } - - if (hostHasDomainSettings) { // The url we are loading has custom domain settings. - // Get a cursor for the current host and move it to the first position. - Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); - currentHostDomainSettingsCursor.moveToFirst(); - - // Get the settings from the cursor. - javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); - firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1); - thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); - domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); - saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); - String userAgentString = (currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT))); - int fontSize = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE))); - - // Close `currentHostDomainSettingsCursor`. - currentHostDomainSettingsCursor.close(); - - // Apply the domain settings. - mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); - mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); - mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); - mainWebView.getSettings().setTextZoom(fontSize); - - // Set third-party cookies status if API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); - } - - // Set the user agent. - if (userAgentString.equals("WebView default user agent")) { - // Set the user agent to `""`, which uses the default value. - mainWebView.getSettings().setUserAgentString(""); - } else { - // Use the selected user agent. - mainWebView.getSettings().setUserAgentString(userAgentString); - } - - // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. - urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_green)); - } else { // The URL we are loading does not have custom domain settings. Load the defaults. - // Get the shared preference values. `this` references the current context. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Store the values from `sharedPreferences` in variables. - javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false); - firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false); - thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false); - domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false); - saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); - String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); - String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); - String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); - - // Apply the default settings. - mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); - mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); - mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); - mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); - - // Set third-party cookies status if API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); - } - - // Set the default user agent. - switch (userAgentString) { - case "WebView default user agent": - // Set the user agent to `""`, which uses the default value. - mainWebView.getSettings().setUserAgentString(""); - break; - - case "Custom user agent": - // Set the custom user agent. - mainWebView.getSettings().setUserAgentString(customUserAgentString); - break; - - default: - // Use the selected user agent. - mainWebView.getSettings().setUserAgentString(userAgentString); - break; - } - - // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. - urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.drawable.url_bar_background_transparent)); - } - - // Close `domainsDatabaseHelper`. - domainsDatabaseHelper.close(); - - // Update the privacy icons, but only if `mainMenu` has already been populated. - if (mainMenu != null) { - updatePrivacyIcons(true); - } - } - } - - public void findPreviousOnPage(View view) { - // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. - mainWebView.findNext(false); - } - - public void findNextOnPage(View view) { - // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. - mainWebView.findNext(true); - } - - public void closeFindOnPage(View view) { - // Delete the contents of `find_on_page_edittext`. - findOnPageEditText.setText(null); - - // Clear the highlighted phrases. - mainWebView.clearMatches(); - - // Hide the Find on Page `RelativeLayout`. - findOnPageLinearLayout.setVisibility(View.GONE); - - // Show the URL app bar. - supportAppBar.setVisibility(View.VISIBLE); - - // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. - inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); - } - - private void applyAppSettings() { - // Get the shared preference values. `this` references the current context. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + private void applyAppSettings() { + // Get a handle for `sharedPreferences`. `this` references the current context. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Store the values from `sharedPreferences` in variables. - String homepageString = sharedPreferences.getString("homepage", "https://duckduckgo.com"); + String homepageString = sharedPreferences.getString("homepage", "https://start.duckduckgo.com"); String torHomepageString = sharedPreferences.getString("tor_homepage", "https://3g2upl4pq6kufc4m.onion"); String torSearchString = sharedPreferences.getString("tor_search", "https://3g2upl4pq6kufc4m.onion/html/?q="); String torSearchCustomURLString = sharedPreferences.getString("tor_search_custom_url", ""); @@ -2133,10 +2208,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false); boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false); boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false); - fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("enable_full_screen_browsing_mode", false); + fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false); hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false); translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true); swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh", false); + displayWebpageImagesBoolean = sharedPreferences.getBoolean("display_webpage_images", true); // Set the homepage, search, and proxy options. if (proxyThroughOrbot) { // Set the Tor options. @@ -2158,6 +2234,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed. OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118"); + // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context. + if (darkTheme) { + appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30)); + } else { + appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50)); + } + // Display a message to the user if we are waiting on Orbot. if (!orbotStatus.equals("ON")) { // Set `waitingForOrbot`. @@ -2185,6 +2268,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Reset the proxy to default. The host is `""` and the port is `"0"`. OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0"); + // Set the default `appBar` background. `this` refers to the context. + if (darkTheme) { + appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900)); + } else { + appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100)); + } + // Reset `waitingForOrbot. waitingForOrbot = false; } @@ -2260,43 +2350,253 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } + // We have to use the deprecated `.getDrawable()` until the minimum API >= 21. + @SuppressWarnings("deprecation") + private void applyDomainSettings(String url) { + // Reset `navigatingHistory`. + navigatingHistory = false; + + // Parse the URL into a URI. + Uri uri = Uri.parse(url); + + // Extract the domain from `uri`. + String hostName = uri.getHost(); + + // Initialize `loadingNewDomainName`. + boolean loadingNewDomainName; + + // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name. + // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.` + //noinspection SimplifiableIfStatement + if ((hostName == null) || (currentDomainName == null)) { + loadingNewDomainName = true; + } else { // Determine if `hostName` equals `currentDomainName`. + loadingNewDomainName = !hostName.equals(currentDomainName); + } + + // Only apply the domain settings if we are loading a new domain. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc. + if (loadingNewDomainName) { + // Set the new `hostname` as the `currentDomainName`. + currentDomainName = hostName; + + // Reset `favoriteIconBitmap` and display it in the `appbar`. + favoriteIconBitmap = favoriteIconDefaultBitmap; + favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true)); + + // Initialize the database handler. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`. + // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. + DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); + + // Get a full cursor from `domainsDatabaseHelper`. + Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain(); + + // Initialize `domainSettingsSet`. + Set domainSettingsSet = new HashSet<>(); + + // Get the domain name column index. + int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME); + + // Populate `domainSettingsSet`. + for (int i = 0; i < domainNameCursor.getCount(); i++) { + // Move `domainsCursor` to the current row. + domainNameCursor.moveToPosition(i); + + // Store the domain name in `domainSettingsSet`. + domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex)); + } + + // Close `domainNameCursor. + domainNameCursor.close(); + + // Initialize variables to track if domain settings will be applied and, if so, under which name. + domainSettingsApplied = false; + String domainNameInDatabase = null; + + // Check the hostname. + if (domainSettingsSet.contains(hostName)) { + domainSettingsApplied = true; + domainNameInDatabase = hostName; + } + + // If `hostName` is not `null`, check all the subdomains of `hostName` against wildcard domains in `domainCursor`. + if (hostName != null) { + while (hostName.contains(".") && !domainSettingsApplied) { // Stop checking if we run out of `.` or if we already know that `domainSettingsApplied` is `true`. + if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`. + domainSettingsApplied = true; + domainNameInDatabase = "*." + hostName; + } + + // Strip out the lowest subdomain of `host`. + hostName = hostName.substring(hostName.indexOf(".") + 1); + } + } + + // Get a handle for the shared preference. `this` references the current context. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + if (domainSettingsApplied) { // The url we are loading has custom domain settings. + // Get a cursor for the current host and move it to the first position. + Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); + currentHostDomainSettingsCursor.moveToFirst(); + + // Get the settings from the cursor. + javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); + firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1); + thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); + domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); + saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); + String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); + int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); + displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); + + // Close `currentHostDomainSettingsCursor`. + currentHostDomainSettingsCursor.close(); + + // Apply the domain settings. + mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); + cookieManager.setAcceptCookie(firstPartyCookiesEnabled); + mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); + mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); + mainWebView.getSettings().setTextZoom(fontSize); + + // Set third-party cookies status if API >= 21. + if (Build.VERSION.SDK_INT >= 21) { + cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); + } + + // Set the user agent. + if (userAgentString.equals("WebView default user agent")) { + // Set the user agent to `""`, which uses the default value. + mainWebView.getSettings().setUserAgentString(""); + } else { + // Use the selected user agent. + mainWebView.getSettings().setUserAgentString(userAgentString); + } + + // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. + if (darkTheme) { + urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); + } else { + urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); + } + } else { // The URL we are loading does not have custom domain settings. Load the defaults. + // Store the values from `sharedPreferences` in variables. + javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false); + firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false); + thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false); + domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false); + saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); + String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); + String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); + + // Apply the default settings. + mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); + cookieManager.setAcceptCookie(firstPartyCookiesEnabled); + mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); + mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); + mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + + // Set third-party cookies status if API >= 21. + if (Build.VERSION.SDK_INT >= 21) { + cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); + } + + // Set the default user agent. + switch (userAgentString) { + case "WebView default user agent": + // Set the user agent to `""`, which uses the default value. + mainWebView.getSettings().setUserAgentString(""); + break; + + case "Custom user agent": + // Set the custom user agent. + mainWebView.getSettings().setUserAgentString(customUserAgentString); + break; + + default: + // Use the selected user agent. + mainWebView.getSettings().setUserAgentString(userAgentString); + break; + } + + // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. + urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent)); + } + + // Close `domainsDatabaseHelper`. + domainsDatabaseHelper.close(); + + // Remove the `onTheFlyDisplayImagesSet` flag and set the display webpage images mode. `true` indicates that custom domain settings are applied. + onTheFlyDisplayImagesSet = false; + setDisplayWebpageImages(); + + // Update the privacy icons, but only if `mainMenu` has already been populated. + if (mainMenu != null) { + updatePrivacyIcons(true); + } + } + } + + private void setDisplayWebpageImages() { + if (!onTheFlyDisplayImagesSet) { + if (domainSettingsApplied) { // Custom domain settings are applied. + switch (displayWebpageImagesInt) { + case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT: + mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImagesBoolean); + break; + + case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED: + mainWebView.getSettings().setLoadsImagesAutomatically(true); + break; + + case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED: + mainWebView.getSettings().setLoadsImagesAutomatically(false); + break; + } + } else { // Default settings are applied. + mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImagesBoolean); + } + } + } + private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) { // Get handles for the icons. - MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript); - MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies); - MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage); - MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData); + MenuItem privacyIconMenuItem = mainMenu.findItem(R.id.toggle_javascript); + MenuItem firstPartyCookiesIconMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies); + MenuItem domStorageIconMenuItem = mainMenu.findItem(R.id.toggle_dom_storage); + MenuItem formDataIconMenuItem = mainMenu.findItem(R.id.toggle_save_form_data); // Update `privacyIcon`. if (javaScriptEnabled) { // JavaScript is enabled. - privacyIcon.setIcon(R.drawable.javascript_enabled); + privacyIconMenuItem.setIcon(R.drawable.javascript_enabled); } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled. - privacyIcon.setIcon(R.drawable.warning); + privacyIconMenuItem.setIcon(R.drawable.warning); } else { // All the dangerous features are disabled. - privacyIcon.setIcon(R.drawable.privacy_mode); + privacyIconMenuItem.setIcon(R.drawable.privacy_mode); } // Update `firstPartyCookiesIcon`. if (firstPartyCookiesEnabled) { // First-party cookies are enabled. - firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled); + firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_enabled); } else { // First-party cookies are disabled. - firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled); + firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled); } // Update `domStorageIcon`. if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled. - domStorageIcon.setIcon(R.drawable.dom_storage_enabled); + domStorageIconMenuItem.setIcon(R.drawable.dom_storage_enabled); } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled. - domStorageIcon.setIcon(R.drawable.dom_storage_disabled); + domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled); } else { // JavaScript is disabled, so DOM storage is ghosted. - domStorageIcon.setIcon(R.drawable.dom_storage_ghosted); + domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted); } // Update `formDataIcon`. if (saveFormDataEnabled) { // Form data is enabled. - formDataIcon.setIcon(R.drawable.form_data_enabled); + formDataIconMenuItem.setIcon(R.drawable.form_data_enabled); } else { // Form data is disabled. - formDataIcon.setIcon(R.drawable.form_data_disabled); + formDataIconMenuItem.setIcon(R.drawable.form_data_disabled); } // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. `this` references the current activity. @@ -2304,4 +2604,23 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation ActivityCompat.invalidateOptionsMenu(this); } } + + private void highlightUrlText() { + String urlString = urlTextBox.getText().toString(); + + if (urlString.startsWith("http://")) { // Highlight connections that are not encrypted. + urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + urlTextBox.getText().setSpan(boldStyleSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else if (urlString.startsWith("https://")) { // Highlight connections that are encrypted. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Get the index of the `/` immediately after the domain name. + int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); + + // De-emphasize the text after the domain name. + if (endOfDomainName > 0) { + urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + } }