/*
- * Copyright © 2015-2017 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
*
* Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
*
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
+// `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
+import android.support.v4.content.pm.ShortcutInfoCompat;
+import android.support.v4.content.pm.ShortcutManagerCompat;
+import android.support.v4.graphics.drawable.IconCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
+import android.util.Log;
import android.util.Patterns;
import android.view.ContextMenu;
import android.view.GestureDetector;
import com.stoutner.privacybrowser.BannerAd;
import com.stoutner.privacybrowser.BuildConfig;
import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.dialogs.AddDomainDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
import java.util.Set;
// We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
-public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener,
+public class MainWebViewActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener, CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener,
DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener,
NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
// `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
public static String webViewTitle;
+ // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`.
+ public static String appliedUserAgentString;
+
// `displayWebpageImagesBoolean` is public static so it can be accessed from `DomainSettingsFragment`. It is also used in `applyAppSettings()` and `applyDomainSettings()`.
public static boolean displayWebpageImagesBoolean;
// `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
public static boolean restartFromBookmarksActivity;
+ // `easyListVersion` is public static so it can be accessed from `AboutTabFragment`. It is also used in `onCreate()`.
+ public static String easyListVersion;
+
// `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and
// `loadBookmarksFolder()`.
public static String currentBookmarksFolder;
- // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
+ // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
public static int domainSettingsDatabaseId;
+
+ // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
public static String pinnedDomainSslIssuedToCNameString;
public static String pinnedDomainSslIssuedToONameString;
public static String pinnedDomainSslIssuedToUNameString;
// `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
private boolean translucentNavigationBarOnFullscreen;
- // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
+ // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
+ private boolean reapplyDomainSettingsOnRestart;
+
+ // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
private String currentDomainName;
// `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
// `waitingForOrbot` is used in `onCreate()` and `applyAppSettings()`.
private boolean waitingForOrbot;
- // `domainSettingsApplied` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
+ // `domainSettingsApplied` is used in `prepareOptionsMenu()`, `applyDomainSettings()`, and `setDisplayWebpageImages()`.
private boolean domainSettingsApplied;
// `displayWebpageImagesInt` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
// `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
private EditText urlTextBox;
- // `redColorSpan` is used in `onCreate()` and `highlightUrlText()`.
+ // The color spans are 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;
// `adView` is used in `onCreate()` and `onConfigurationChanged()`.
// Run the default commands.
super.onCreate(savedInstanceState);
+ // **DEBUG** Log the beginning of the loading of the ad blocker.
+ Log.i("AdBlocker", "Begin loading ad blocker");
+
+ // Initialize `adServerSet`.
+ final Set<String> adServersSet = new HashSet<>();
+
+ // Load the list of ad servers into memory.
+ try {
+ // Load `easylist.txt` into a `BufferedReader`.
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("easylist.txt")));
+
+ // Create a string for storing each ad server.
+ String adBlockerEntry;
+
+ // Populate `adServersSet`.
+ while ((adBlockerEntry = bufferedReader.readLine()) != null) {
+ //noinspection StatementWithEmptyBody
+ if (adBlockerEntry.contains("##") || adBlockerEntry.contains("#?#") || adBlockerEntry.contains("#@#") || adBlockerEntry.startsWith("[")) {
+ // Entries that contain `##`, `#?#`, and `#@#` are for hiding elements in the main page's HTML. Entries that start with `[` describe the AdBlock compatibility level.
+
+ // Do nothing. Privacy Browser does not currently use these entries.
+
+ // **DEBUG** Log the entries that are not added.
+ // Log.i("AdBlocker", "Not added: " + adBlockerEntry);
+ } else if (adBlockerEntry.startsWith("!")){ // Entries that begin with `!` are comments.
+ if (adBlockerEntry.startsWith("! Version:")) {
+ // Store the EasyList version number.
+ easyListVersion = adBlockerEntry.substring(11);
+ }
+
+ // **DEBUG** Log the entries that are not added.
+ // Log.i("AdBlocker", "Not added: " + adBlockerEntry);
+ } else {
+ adServersSet.add(adBlockerEntry);
+ }
+ }
+
+ // Close `bufferedReader`.
+ bufferedReader.close();
+ } catch (IOException e) {
+ // The asset exists, so the `IOException` will never be thrown.
+ }
+
+ // **DEBUG** Log the finishing of the loading of the ad blocker.
+ Log.i("AdBlocker", "Finish loading ad blocker");
+
// Set the content view.
setContentView(R.layout.main_drawerlayout);
appBar.setCustomView(R.layout.url_app_bar);
appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
- // Initialize the `ForegroundColorSpans` and `StyleSpan` for highlighting `urlTextBox`. We have to use the deprecated `getColor()` until API >= 23.
+ // Initialize the foreground color spans for highlighting the URLs. 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));
// Get a handle for `urlTextBox`.
- urlTextBox = appBar.getCustomView().findViewById(R.id.url_edittext);
+ urlTextBox = findViewById(R.id.url_edittext);
// Remove the formatting from `urlTextBar` when the user is editing the text.
- urlTextBox.setOnFocusChangeListener((v, hasFocus) -> {
+ urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
if (hasFocus) { // The user is editing `urlTextBox`.
// Remove the highlighting.
urlTextBox.getText().removeSpan(redColorSpan);
}
});
- // Set the `Go` button on the keyboard to load the URL in `urlTextBox`.
- urlTextBox.setOnKeyListener((v, keyCode, event) -> {
+ // Set the go button on the keyboard to load the URL in `urlTextBox`.
+ urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
// If the event is a key-down event on the `enter` button, load the URL.
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
// Load the URL into the mainWebView and consume the event.
// drawerToggle creates the hamburger icon at the start of the AppBar.
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
- // Initialize `adServerSet`.
- final Set<String> adServersSet = new HashSet<>();
-
- // Load the list of ad servers into memory.
- try {
- // Load `pgl.yoyo.org_adservers.txt` into a `BufferedReader`.
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("pgl.yoyo.org_adservers.txt")));
-
- // Create a string for storing each ad server.
- String adServer;
-
- // Populate `adServersSet`.
- while ((adServer = bufferedReader.readLine()) != null) {
- adServersSet.add(adServer);
- }
-
- // Close `bufferedReader`.
- bufferedReader.close();
- } catch (IOException ioException) {
- // We're pretty sure the asset exists, so we don't need to worry about the `IOException` ever being thrown.
- }
-
mainWebView.setWebViewClient(new WebViewClient() {
// `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
// We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
@SuppressWarnings("deprecation")
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
- 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.
+ if (url.startsWith("mailto:")) { // Load the email address in an external email program.
+ // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
// Parse the url and set it as the data for the `Intent`.
// Make it so.
startActivity(emailIntent);
+ // Returning `true` indicates the application is handling the URL.
+ return true;
+ } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
+ // `ACTION_DIAL` open the dialer and loads the phone number, but waits for the user to place the call.
+ Intent dialIntent = new Intent(Intent.ACTION_DIAL);
+
+ // Add the phone number to the intent.
+ dialIntent.setData(Uri.parse(url));
+
+ // `FLAG_ACTIVITY_NEW_TASK` opens the dialer in a new task instead as part of Privacy Browser.
+ dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Make it so.
+ startActivity(dialIntent);
+
// Returning `true` indicates the application is handling the URL.
return true;
} else { // Load the URL in Privacy Browser.
});
}
+ // Update the progress bar.
progressBar.setProgress(progress);
+
+ // Set the visibility of the progress bar.
if (progress < 100) {
// Show the progress bar.
progressBar.setVisibility(View.VISIBLE);
registerForContextMenu(mainWebView);
// Allow the downloading of files.
- mainWebView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
+ mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
// Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`.
AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
@Override
public void onRestart() {
+ // Run the default commands.
super.onRestart();
// Apply the app settings, which may have been changed in `SettingsActivity`.
applyAppSettings();
+ // Apply the domain settings if returning from the Domains Activity.
+ if (reapplyDomainSettingsOnRestart) {
+ // Reset `reapplyDomainSettingsOnRestart`.
+ reapplyDomainSettingsOnRestart = false;
+
+ // Reapply the domain settings.
+ applyDomainSettings(formattedUrlString);
+ }
+
// Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
updatePrivacyIcons(true);
// `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
@Override
public void onResume() {
+ // Run the default commands.
super.onResume();
// Resume JavaScript (if enabled).
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// Get handles for the menu items.
+ MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
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 clearDataMenuItem = menu.findItem(R.id.clear_data);
MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);
MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
+ // Set the text for the domain menu item.
+ if (domainSettingsApplied) {
+ addOrEditDomain.setTitle(R.string.edit_domain_settings);
+ } else {
+ addOrEditDomain.setTitle(R.string.add_domain_settings);
+ }
+
// Set the status of the menu item checkboxes.
toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
+ // Enable `Clear Data` if any of the submenu items are enabled.
+ clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
+
// Initialize font size variables.
int fontSize = mainWebView.getSettings().getTextZoom();
String fontSizeTitle;
// Run all the other default commands.
super.onPrepareOptionsMenu(menu);
- // `return true` displays the menu.
+ // Display the menu.
return true;
}
// removeAllCookies is deprecated, but it is required for API < 21.
@SuppressWarnings("deprecation")
public boolean onOptionsItemSelected(MenuItem menuItem) {
+ // Get the selected menu item ID.
int menuItemId = menuItem.getItemId();
// Set the commands that relate to the menu entries.
switch (menuItemId) {
+ case R.id.add_or_edit_domain:
+ if (domainSettingsApplied) { // Edit the current domain settings.
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
+ currentDomainName = "";
+
+ // Create an intent to launch the domains activity.
+ Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+ // Put extra information instructing the domains activity to directly load the current domain.
+ domainsIntent.putExtra("LoadDomain", domainSettingsDatabaseId);
+
+ // Make it so.
+ startActivity(domainsIntent);
+ } else { // Add a new domain.
+ // Show the add domain `AlertDialog`.
+ AppCompatDialogFragment addDomainDialog = new AddDomainDialog();
+ addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain));
+ }
+ return true;
+
case R.id.toggle_javascript:
// Switch the status of javaScriptEnabled.
javaScriptEnabled = !javaScriptEnabled;
WebStorage webStorage = WebStorage.getInstance();
webStorage.deleteAllData();
- // Manually remove `IndexedDB` if it exists.
+ // Manually delete the DOM storage files and directories.
try {
+ // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+ privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+ // Multiple commands must be used because `Runtime.exec()` does not like `*`.
privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+ privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+ privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+ privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
} catch (IOException e) {
// Do nothing if an error is thrown.
}
printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
return true;
+ case R.id.view_source:
+ // Launch the Vew Source activity.
+ Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
+ startActivity(viewSourceIntent);
+ return true;
+
case R.id.add_to_homescreen:
// Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
break;
case R.id.domains:
- // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`.
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
currentDomainName = "";
// Launch `DomainsActivity`.
break;
case R.id.settings:
- // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`.
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
currentDomainName = "";
// Launch `SettingsActivity`.
// Manually delete the DOM storage files and directories, 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.
+ // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
- // We have to use multiple commands because `Runtime.exec()` does not like `*`.
+ // Multiple commands must be used because `Runtime.exec()` does not like `*`.
privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
});
// Add a `Download Image` entry.
- menu.add(R.string.download_image).setOnMenuItemClickListener(item -> {
+ menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
// Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
});
// Add a `Download Image` entry.
- menu.add(R.string.download_image).setOnMenuItemClickListener(item -> {
+ menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
// Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
}
}
+ @Override
+ public void onAddDomain(AppCompatDialogFragment dialogFragment) {
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
+ currentDomainName = "";
+
+ // Get the new domain name `String` from `dialogFragment`.
+ EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
+ String domainNameString = domainNameEditText.getText().toString();
+
+ // 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);
+
+ // Create the domain and store the database ID in `currentDomainDatabaseId`.
+ int newDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString);
+
+ // Create an intent to launch the domains activity.
+ Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+ // Put extra information instructing the domains activity to directly load the current domain.
+ domainsIntent.putExtra("LoadDomain", newDomainDatabaseId);
+
+ // Make it so.
+ startActivity(domainsIntent);
+ }
+
@Override
public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
// Get the `EditTexts` from the `dialogFragment`.
@Override
public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
- // Get shortcutNameEditText from the alert dialog.
+ // Get the shortcut name.
EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
+ String shortcutNameString = shortcutNameEditText.getText().toString();
+
+ // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
+ IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
+
+ // Setup the shortcut intent.
+ Intent shortcutIntent = new Intent();
+ shortcutIntent.setAction(Intent.ACTION_VIEW);
+ shortcutIntent.setData(Uri.parse(formattedUrlString));
- // Create the bookmark shortcut based on formattedUrlString.
- Intent bookmarkShortcut = new Intent();
- bookmarkShortcut.setAction(Intent.ACTION_VIEW);
- bookmarkShortcut.setData(Uri.parse(formattedUrlString));
-
- // Place the bookmark shortcut on the home screen.
- Intent placeBookmarkShortcut = new Intent();
- placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
- placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
- placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIconBitmap);
- placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
- sendBroadcast(placeBookmarkShortcut);
+ // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
+ ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
+
+ // Add the required fields to the shortcut info builder.
+ shortcutInfoBuilder.setIcon(favoriteIcon);
+ shortcutInfoBuilder.setIntent(shortcutIntent);
+ shortcutInfoBuilder.setShortLabel(shortcutNameString);
+
+ // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
+ ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
}
@Override
}
private void applyAppSettings() {
- // Get a handle for `sharedPreferences`. `this` references the current context.
+ // Get a handle for the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- // Store the values from `sharedPreferences` in variables.
+ // Store the values from the shared preferences in variables.
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=");
// Use the selected user agent.
mainWebView.getSettings().setUserAgentString(userAgentString);
}
+
+ // Store the applied user agent string.
+ appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
}
// 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.
// Use the selected user agent.
mainWebView.getSettings().setUserAgentString(defaultUserAgentString);
}
+
+ // Store the applied user agent string.
+ appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
}
// Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
private void highlightUrlText() {
String urlString = urlTextBox.getText().toString();
- if (urlString.startsWith("http://")) { // Highlight connections that are not encrypted.
+ if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else if (urlString.startsWith("https://")) { // Highlight connections that are encrypted.
+ } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}