2 * Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.activities;
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.res.Resources;
27 import android.database.Cursor;
28 import android.net.http.SslCertificate;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.view.Menu;
32 import android.view.MenuItem;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.view.WindowManager;
36 import android.widget.CursorAdapter;
37 import android.widget.EditText;
38 import android.widget.ListView;
39 import android.widget.RadioButton;
40 import android.widget.Spinner;
41 import android.widget.Switch;
42 import android.widget.TextView;
44 import androidx.appcompat.app.ActionBar;
45 import androidx.appcompat.app.AppCompatActivity;
46 import androidx.appcompat.widget.Toolbar; // The AndroidX toolbar must be used until the minimum API is >= 21.
47 import androidx.core.app.NavUtils;
48 import androidx.fragment.app.DialogFragment;
49 import androidx.fragment.app.FragmentManager; // The AndroidX dialog fragment must be used or an error is produced on API <=22.
51 import com.google.android.material.floatingactionbutton.FloatingActionButton;
52 import com.google.android.material.snackbar.Snackbar;
54 import com.stoutner.privacybrowser.R;
55 import com.stoutner.privacybrowser.dialogs.AddDomainDialog;
56 import com.stoutner.privacybrowser.fragments.DomainSettingsFragment;
57 import com.stoutner.privacybrowser.fragments.DomainsListFragment;
58 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
60 import java.util.Objects;
62 public class DomainsActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener, DomainsListFragment.DismissSnackbarInterface {
63 // `twoPanedMode` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `populateDomainsListView()`.
64 public static boolean twoPanedMode;
66 // `databaseId` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onCreateOptionsMenu()`, `saveDomainSettings()` and `populateDomainsListView()`.
67 public static int currentDomainDatabaseId;
69 // `deleteMenuItem` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`.
70 public static MenuItem deleteMenuItem;
72 // `dismissingSnackbar` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onOptionsItemSelected()`.
73 public static boolean dismissingSnackbar;
75 // The SSL certificate and current IP addresses are used to update pinned settings.
76 public static SslCertificate currentSslCertificate;
77 public static String currentIpAddresses;
80 // `closeActivityAfterDismissingSnackbar` is used in `onOptionsItemSelected()`, and `onBackPressed()`.
81 private boolean closeActivityAfterDismissingSnackbar;
83 // The undelete snackbar is used in `onOptionsItemSelected()` and `onBackPressed()`.
84 private Snackbar undoDeleteSnackbar;
86 // `domainsDatabaseHelper` is used in `onCreate()`, `saveDomainSettings()`, and `onDestroy()`.
87 private static DomainsDatabaseHelper domainsDatabaseHelper;
89 // `domainsListView` is used in `onCreate()` and `populateDomainsList()`.
90 private ListView domainsListView;
92 // `addDomainFAB` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`.
93 private FloatingActionButton addDomainFAB;
95 // `deletedDomainPosition` is used in an inner and outer class in `onOptionsItemSelected()`.
96 private int deletedDomainPosition;
98 // `restartAfterRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
99 private boolean restartAfterRotate;
101 // `domainSettingsDisplayedBeforeRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
102 private boolean domainSettingsDisplayedBeforeRotate;
104 // `domainSettingsDatabaseIdBeforeRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
105 private int domainSettingsDatabaseIdBeforeRotate;
107 // `goDirectlyToDatabaseId` is used in `onCreate()` and `onCreateOptionsMenu()`.
108 private int goDirectlyToDatabaseId;
110 // `closeOnBack` is used in `onCreate()`, `onOptionsItemSelected()` and `onBackPressed()`.
111 private boolean closeOnBack;
113 // `coordinatorLayout` is use in `onCreate()`, `onOptionsItemSelected()`, and `onSaveInstanceState()`.
114 private View coordinatorLayout;
116 // `resources` is used in `onCreate()`, `onOptionsItemSelected()`, and `onSaveInstanceState()`.
117 private Resources resources;
120 protected void onCreate(Bundle savedInstanceState) {
121 // Disable screenshots if not allowed.
122 if (!MainWebViewActivity.allowScreenshots) {
123 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
126 // Set the activity theme.
127 if (MainWebViewActivity.darkTheme) {
128 setTheme(R.style.PrivacyBrowserDark_SecondaryActivity);
130 setTheme(R.style.PrivacyBrowserLight_SecondaryActivity);
133 // Run the default commands.
134 super.onCreate(savedInstanceState);
136 // Extract the values from `savedInstanceState` if it is not `null`.
137 if (savedInstanceState != null) {
138 restartAfterRotate = true;
139 domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean("domain_settings_displayed");
140 domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt("domain_settings_database_id");
143 // Get the launching intent
144 Intent intent = getIntent();
146 // Extract the domain to load if there is one. `-1` is the default value.
147 goDirectlyToDatabaseId = intent.getIntExtra("load_domain", -1);
149 // Get the status of close-on-back, which is true when the domains activity is called from the options menu.
150 closeOnBack = intent.getBooleanExtra("close_on_back", false);
152 // Set the content view.
153 setContentView(R.layout.domains_coordinatorlayout);
155 // Populate the class variables.
156 coordinatorLayout = findViewById(R.id.domains_coordinatorlayout);
157 resources = getResources();
159 // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
160 final Toolbar toolbar = findViewById(R.id.domains_toolbar);
161 setSupportActionBar(toolbar);
163 // Get a handle for the action bar.
164 ActionBar actionBar = getSupportActionBar();
166 // Remove the incorrect lint warning that the action bar might be null.
167 assert actionBar != null;
169 // Set the back arrow on the action bar.
170 actionBar.setDisplayHomeAsUpEnabled(true);
172 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
173 domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
175 // Determine if we are in two pane mode. `domain_settings_fragment_container` does not exist on devices with a width less than 900dp.
176 twoPanedMode = (findViewById(R.id.domain_settings_fragment_container) != null);
178 // Configure `addDomainFAB`.
179 addDomainFAB = findViewById(R.id.add_domain_fab);
180 addDomainFAB.setOnClickListener((View view) -> {
181 // Show the add domain `AlertDialog`.
182 DialogFragment addDomainDialog = new AddDomainDialog();
183 addDomainDialog.show(getSupportFragmentManager(), resources.getString(R.string.add_domain));
188 public boolean onCreateOptionsMenu(Menu menu) {
190 getMenuInflater().inflate(R.menu.domains_options_menu, menu);
192 // Store `deleteMenuItem` for future use.
193 deleteMenuItem = menu.findItem(R.id.delete_domain);
195 // Only display `deleteMenuItem` (initially) in two-paned mode.
196 deleteMenuItem.setVisible(twoPanedMode);
198 // Get a handle for the fragment manager.
199 FragmentManager fragmentManager = getSupportFragmentManager();
201 // Display the fragments. This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated.
202 if (restartAfterRotate && domainSettingsDisplayedBeforeRotate) { // The device was rotated and domain settings were displayed previously.
203 if (twoPanedMode) { // The device is in two-paned mode.
204 // Reset `restartAfterRotate`.
205 restartAfterRotate = false;
207 // Display `DomainsListFragment`.
208 DomainsListFragment domainsListFragment = new DomainsListFragment();
209 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
210 fragmentManager.executePendingTransactions();
212 // Populate the list of domains. `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation.
213 populateDomainsListView(domainSettingsDatabaseIdBeforeRotate);
214 } else { // The device is in single-paned mode.
215 // Reset `restartAfterRotate`.
216 restartAfterRotate = false;
218 // Store the current domain database ID.
219 currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRotate;
221 // Add `currentDomainDatabaseId` to `argumentsBundle`.
222 Bundle argumentsBundle = new Bundle();
223 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
225 // Add `argumentsBundle` to `domainSettingsFragment`.
226 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
227 domainSettingsFragment.setArguments(argumentsBundle);
229 // Show `deleteMenuItem`.
230 deleteMenuItem.setVisible(true);
232 // Hide `add_domain_fab`.
235 // Display `domainSettingsFragment`.
236 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
238 } else { // The device was not rotated or, if it was, domain settings were not displayed previously.
239 if (goDirectlyToDatabaseId >=0) { // Load the indicated domain settings.
240 // Store the current domain database ID.
241 currentDomainDatabaseId = goDirectlyToDatabaseId;
243 if (twoPanedMode) { // The device is in two-paned mode.
244 // Display `DomainsListFragment`.
245 DomainsListFragment domainsListFragment = new DomainsListFragment();
246 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
247 fragmentManager.executePendingTransactions();
249 // Populate the list of domains. `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation.
250 populateDomainsListView(goDirectlyToDatabaseId);
251 } else { // The device is in single-paned mode.
252 // Add the domain ID to be loaded to `argumentsBundle`.
253 Bundle argumentsBundle = new Bundle();
254 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId);
256 // Add `argumentsBundle` to `domainSettingsFragment`.
257 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
258 domainSettingsFragment.setArguments(argumentsBundle);
260 // Show `deleteMenuItem`.
261 deleteMenuItem.setVisible(true);
263 // Hide `add_domain_fab`.
266 // Display `domainSettingsFragment`.
267 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
269 } else { // Highlight the first domain.
270 // Display `DomainsListFragment`.
271 DomainsListFragment domainsListFragment = new DomainsListFragment();
272 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
273 fragmentManager.executePendingTransactions();
275 // Populate the list of domains. `-1` highlights the first domain.
276 populateDomainsListView(-1);
285 public boolean onOptionsItemSelected(MenuItem menuItem) {
286 // Get the ID of the menu item that was selected.
287 int menuItemID = menuItem.getItemId();
289 // Get a handle for the fragment manager.
290 FragmentManager fragmentManager = getSupportFragmentManager();
292 switch (menuItemID) {
293 case android.R.id.home: // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
294 if (twoPanedMode) { // The device is in two-paned mode.
295 // Save the current domain settings if the domain settings fragment is displayed.
296 if (findViewById(R.id.domain_settings_scrollview) != null) {
297 saveDomainSettings(coordinatorLayout, resources);
300 // Dismiss the undo delete `SnackBar` if it is shown.
301 if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
302 // Set the close flag.
303 closeActivityAfterDismissingSnackbar = true;
305 // Dismiss the snackbar.
306 undoDeleteSnackbar.dismiss();
309 NavUtils.navigateUpFromSameTask(this);
311 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
312 // Save the current domain settings.
313 saveDomainSettings(coordinatorLayout, resources);
316 NavUtils.navigateUpFromSameTask(this);
317 } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed.
318 // Save the current domain settings.
319 saveDomainSettings(coordinatorLayout, resources);
321 // Display the domains list fragment.
322 DomainsListFragment domainsListFragment = new DomainsListFragment();
323 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
324 fragmentManager.executePendingTransactions();
326 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
327 populateDomainsListView(-1);
329 // Show the add domain FAB.
332 // Hide the delete menu item.
333 deleteMenuItem.setVisible(false);
334 } else { // The device is in single-paned mode and `DomainsListFragment` is displayed.
335 // Dismiss the undo delete `SnackBar` if it is shown.
336 if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
337 // Set the close flag.
338 closeActivityAfterDismissingSnackbar = true;
340 // Dismiss the snackbar.
341 undoDeleteSnackbar.dismiss();
344 NavUtils.navigateUpFromSameTask(this);
349 case R.id.delete_domain:
350 // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists.
353 // Store a copy of `currentDomainDatabaseId` because it could change while the `Snackbar` is displayed.
354 final int databaseIdToDelete = currentDomainDatabaseId;
356 // Update the fragments and menu items.
357 if (twoPanedMode) { // Two-paned mode.
358 // Store the deleted domain position, which is needed if `Undo` is selected in the `Snackbar`.
359 deletedDomainPosition = domainsListView.getCheckedItemPosition();
361 // Disable the options `MenuItems`.
362 deleteMenuItem.setEnabled(false);
363 deleteMenuItem.setIcon(R.drawable.delete_blue);
365 // Remove the domain settings fragment.
366 fragmentManager.beginTransaction().remove(Objects.requireNonNull(fragmentManager.findFragmentById(R.id.domain_settings_fragment_container))).commit();
367 } else { // Single-paned mode.
368 // Display `DomainsListFragment`.
369 DomainsListFragment domainsListFragment = new DomainsListFragment();
370 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
371 fragmentManager.executePendingTransactions();
373 // Show the add domain FAB.
376 // Hide `deleteMenuItem`.
377 deleteMenuItem.setVisible(false);
380 // Get a `Cursor` that does not show the domain to be deleted.
381 Cursor domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete);
383 // Setup `domainsPendingDeleteCursorAdapter` with `this` context. `false` disables `autoRequery`.
384 CursorAdapter domainsPendingDeleteCursorAdapter = new CursorAdapter(this, domainsPendingDeleteCursor, false) {
386 public View newView(Context context, Cursor cursor, ViewGroup parent) {
387 // Inflate the individual item layout. `false` does not attach it to the root.
388 return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
392 public void bindView(View view, Context context, Cursor cursor) {
393 // Set the domain name.
394 String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
395 TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
396 domainNameTextView.setText(domainNameString);
400 // Update the handle for the current `domains_listview`.
401 domainsListView = findViewById(R.id.domains_listview);
403 // Update the `ListView`.
404 domainsListView.setAdapter(domainsPendingDeleteCursorAdapter);
406 // Get a handle for the activity.
407 Activity activity = this;
409 // Display a `Snackbar`.
410 undoDeleteSnackbar = Snackbar.make(domainsListView, R.string.domain_deleted, Snackbar.LENGTH_LONG)
411 .setAction(R.string.undo, (View v) -> {
412 // Do nothing because everything will be handled by `onDismissed()` below.
414 .addCallback(new Snackbar.Callback() {
415 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
417 public void onDismissed(Snackbar snackbar, int event) {
419 // The user pushed the `Undo` button.
420 case Snackbar.Callback.DISMISS_EVENT_ACTION:
421 // Store `databaseId` in `argumentsBundle`.
422 Bundle argumentsBundle = new Bundle();
423 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete);
425 // Add `argumentsBundle` to `domainSettingsFragment`.
426 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
427 domainSettingsFragment.setArguments(argumentsBundle);
429 // Display the correct fragments.
430 if (twoPanedMode) { // The device in in two-paned mode.
431 // Get a `Cursor` with the current contents of the domains database.
432 Cursor undoDeleteDomainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
434 // Setup `domainsCursorAdapter` with `this` context. `false` disables `autoRequery`.
435 CursorAdapter undoDeleteDomainsCursorAdapter = new CursorAdapter(getApplicationContext(), undoDeleteDomainsCursor, false) {
437 public View newView(Context context, Cursor cursor, ViewGroup parent) {
438 // Inflate the individual item layout. `false` does not attach it to the root.
439 return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
443 public void bindView(View view, Context context, Cursor cursor) {
444 // Set the domain name.
445 String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
446 TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
447 domainNameTextView.setText(domainNameString);
451 // Update the `ListView`.
452 domainsListView.setAdapter(undoDeleteDomainsCursorAdapter);
453 // Select the previously deleted domain in `domainsListView`.
454 domainsListView.setItemChecked(deletedDomainPosition, true);
456 // Display `domainSettingsFragment`.
457 fragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit();
459 // Enable the options `MenuItems`.
460 deleteMenuItem.setEnabled(true);
461 deleteMenuItem.setIcon(R.drawable.delete_light);
462 } else { // The device in in one-paned mode.
463 // Display `domainSettingsFragment`.
464 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
466 // Hide the add domain FAB.
469 // Show and enable `deleteMenuItem`.
470 deleteMenuItem.setVisible(true);
472 // Display `domainSettingsFragment`.
473 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
477 // The `Snackbar` was dismissed without the `Undo` button being pushed.
479 // Delete the selected domain.
480 domainsDatabaseHelper.deleteDomain(databaseIdToDelete);
482 // Enable the delete menu item if the system was waiting for a snackbar to be dismissed.
483 if (dismissingSnackbar) {
484 // Create a `Runnable` to enable the delete menu item.
485 Runnable enableDeleteMenuItemRunnable = () -> {
486 // Enable `deleteMenuItem` according to the display mode.
487 if (twoPanedMode) { // Two-paned mode.
488 // Enable `deleteMenuItem`.
489 deleteMenuItem.setEnabled(true);
491 // Set the delete icon according to the theme.
492 if (MainWebViewActivity.darkTheme) {
493 deleteMenuItem.setIcon(R.drawable.delete_dark);
495 deleteMenuItem.setIcon(R.drawable.delete_light);
497 } else { // Single-paned mode.
498 // Show `deleteMenuItem`.
499 deleteMenuItem.setVisible(true);
502 // Reset `dismissingSnackbar`.
503 dismissingSnackbar = false;
506 // Enable the delete menu icon after 100 milliseconds to make sure that the previous domain has been deleted from the database.
507 Handler handler = new Handler();
508 handler.postDelayed(enableDeleteMenuItemRunnable, 100);
511 // Close the activity if back was pressed.
512 if (closeActivityAfterDismissingSnackbar) {
514 NavUtils.navigateUpFromSameTask(activity);
522 // Show the Snackbar.
523 undoDeleteSnackbar.show();
527 // Consume the event.
532 protected void onSaveInstanceState(Bundle outState) {
533 // Store the current `DomainSettingsFragment` state in `outState`.
534 if (findViewById(R.id.domain_settings_scrollview) != null) { // `DomainSettingsFragment` is displayed.
535 // Save any changes that have been made to the domain settings.
536 saveDomainSettings(coordinatorLayout, resources);
538 // Store `DomainSettingsDisplayed`.
539 outState.putBoolean("domain_settings_displayed", true);
540 outState.putInt("domain_settings_database_id", DomainSettingsFragment.databaseId);
541 } else { // `DomainSettingsFragment` is not displayed.
542 outState.putBoolean("domain_settings_displayed", false);
543 outState.putInt("domain_settings_database_id", -1);
546 super.onSaveInstanceState(outState);
549 // Control what the navigation bar back button does.
551 public void onBackPressed() {
552 // Get a handle for the fragment manager.
553 FragmentManager fragmentManager = getSupportFragmentManager();
555 if (twoPanedMode) { // The device is in two-paned mode.
556 // Save the current domain settings if the domain settings fragment is displayed.
557 if (findViewById(R.id.domain_settings_scrollview) != null) {
558 saveDomainSettings(coordinatorLayout, resources);
561 // Dismiss the undo delete SnackBar if it is shown.
562 if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) {
563 // Set the close flag.
564 closeActivityAfterDismissingSnackbar = true;
566 // Dismiss the snackbar.
567 undoDeleteSnackbar.dismiss();
569 // Pass `onBackPressed()` to the system.
570 super.onBackPressed();
572 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
573 // Save the current domain settings.
574 saveDomainSettings(coordinatorLayout, resources);
576 // Pass `onBackPressed()` to the system.
577 super.onBackPressed();
578 } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and domain settings fragment is displayed.
579 // Save the current domain settings.
580 saveDomainSettings(coordinatorLayout, resources);
582 // Display the domains list fragment.
583 DomainsListFragment domainsListFragment = new DomainsListFragment();
584 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
585 fragmentManager.executePendingTransactions();
587 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
588 populateDomainsListView(-1);
590 // Show the add domain FAB.
593 // Hide the delete menu item.
594 deleteMenuItem.setVisible(false);
595 } else { // The device is in single-paned mode and the domain list fragment is displayed.
596 // Dismiss the undo delete SnackBar if it is shown.
597 if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) {
598 // Set the close flag.
599 closeActivityAfterDismissingSnackbar = true;
601 // Dismiss the snackbar.
602 undoDeleteSnackbar.dismiss();
604 // Pass `onBackPressed()` to the system.
605 super.onBackPressed();
611 public void onAddDomain(DialogFragment dialogFragment) {
612 // Dismiss the undo delete snackbar if it is currently displayed.
613 if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) {
614 undoDeleteSnackbar.dismiss();
617 // Get the new domain name String from the dialog fragment.
618 EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
619 String domainNameString = domainNameEditText.getText().toString();
621 // Create the domain and store the database ID in `currentDomainDatabaseId`.
622 currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString);
624 // Display the newly created domain.
625 if (twoPanedMode) { // The device in in two-paned mode.
626 populateDomainsListView(currentDomainDatabaseId);
627 } else { // The device is in single-paned mode.
628 // Hide the add domain FAB.
631 // Show and enable `deleteMenuItem`.
632 DomainsActivity.deleteMenuItem.setVisible(true);
634 // Add the current domain database ID to the arguments bundle.
635 Bundle argumentsBundle = new Bundle();
636 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
638 // Add and arguments bundle to the domain setting fragment.
639 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
640 domainSettingsFragment.setArguments(argumentsBundle);
642 // Display the domain settings fragment.
643 getSupportFragmentManager().beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
647 public void saveDomainSettings(View view, Resources resources) {
648 // Get handles for the domain settings.
649 EditText domainNameEditText = view.findViewById(R.id.domain_settings_name_edittext);
650 Switch javaScriptSwitch = view.findViewById(R.id.javascript_switch);
651 Switch firstPartyCookiesSwitch = view.findViewById(R.id.first_party_cookies_switch);
652 Switch thirdPartyCookiesSwitch = view.findViewById(R.id.third_party_cookies_switch);
653 Switch domStorageSwitch = view.findViewById(R.id.dom_storage_switch);
654 Switch formDataSwitch = view.findViewById(R.id.form_data_switch); // Form data can be removed once the minimum API >= 26.
655 Switch easyListSwitch = view.findViewById(R.id.easylist_switch);
656 Switch easyPrivacySwitch = view.findViewById(R.id.easyprivacy_switch);
657 Switch fanboysAnnoyanceSwitch = view.findViewById(R.id.fanboys_annoyance_list_switch);
658 Switch fanboysSocialBlockingSwitch = view.findViewById(R.id.fanboys_social_blocking_list_switch);
659 Switch ultraPrivacySwitch = view.findViewById(R.id.ultraprivacy_switch);
660 Switch blockAllThirdPartyRequestsSwitch = view.findViewById(R.id.block_all_third_party_requests_switch);
661 Spinner userAgentSpinner = view.findViewById(R.id.user_agent_spinner);
662 EditText customUserAgentEditText = view.findViewById(R.id.custom_user_agent_edittext);
663 Spinner fontSizeSpinner = view.findViewById(R.id.font_size_spinner);
664 Spinner swipeToRefreshSpinner = view.findViewById(R.id.swipe_to_refresh_spinner);
665 Spinner displayWebpageImagesSpinner = view.findViewById(R.id.display_webpage_images_spinner);
666 Spinner nightModeSpinner = view.findViewById(R.id.night_mode_spinner);
667 Switch pinnedSslCertificateSwitch = view.findViewById(R.id.pinned_ssl_certificate_switch);
668 RadioButton currentWebsiteCertificateRadioButton = view.findViewById(R.id.current_website_certificate_radiobutton);
669 Switch pinnedIpAddressesSwitch = view.findViewById(R.id.pinned_ip_addresses_switch);
670 RadioButton currentIpAddressesRadioButton = view.findViewById(R.id.current_ip_addresses_radiobutton);
671 TextView currentIpAddressesTextView = view.findViewById(R.id.current_ip_addresses_textview);
673 // Extract the data for the domain settings.
674 String domainNameString = domainNameEditText.getText().toString();
675 boolean javaScriptEnabled = javaScriptSwitch.isChecked();
676 boolean firstPartyCookiesEnabled = firstPartyCookiesSwitch.isChecked();
677 boolean thirdPartyCookiesEnabled = thirdPartyCookiesSwitch.isChecked();
678 boolean domStorageEnabled = domStorageSwitch.isChecked();
679 boolean formDataEnabled = formDataSwitch.isChecked(); // Form data can be removed once the minimum API >= 26.
680 boolean easyListEnabled = easyListSwitch.isChecked();
681 boolean easyPrivacyEnabled = easyPrivacySwitch.isChecked();
682 boolean fanboysAnnoyanceEnabled = fanboysAnnoyanceSwitch.isChecked();
683 boolean fanboysSocialBlockingEnabled = fanboysSocialBlockingSwitch.isChecked();
684 boolean ultraPrivacyEnabled = ultraPrivacySwitch.isChecked();
685 boolean blockAllThirdPartyRequests = blockAllThirdPartyRequestsSwitch.isChecked();
686 int userAgentPosition = userAgentSpinner.getSelectedItemPosition();
687 int fontSizePosition = fontSizeSpinner.getSelectedItemPosition();
688 int swipeToRefreshInt = swipeToRefreshSpinner.getSelectedItemPosition();
689 int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
690 int nightModeInt = nightModeSpinner.getSelectedItemPosition();
691 boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
692 boolean pinnedIpAddress = pinnedIpAddressesSwitch.isChecked();
694 // Initialize the user agent name string.
695 String userAgentName;
697 // Set the user agent name.
698 switch (userAgentPosition) {
699 case MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT:
700 // Set the user agent name to be `System default user agent`.
701 userAgentName = resources.getString(R.string.system_default_user_agent);
704 case MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT:
705 // Set the user agent name to be the custom user agent.
706 userAgentName = customUserAgentEditText.getText().toString();
710 // Get the array of user agent names.
711 String[] userAgentNameArray = resources.getStringArray(R.array.user_agent_names);
713 // Set the user agent name from the array. The domain spinner has one more entry than the name array, so the position must be decremented.
714 userAgentName = userAgentNameArray[userAgentPosition - 1];
717 // Get the font size integer.
718 int fontSizeInt = Integer.parseInt(resources.getStringArray(R.array.domain_settings_font_size_entry_values)[fontSizePosition]);
720 // Save the domain settings.
721 domainsDatabaseHelper.updateDomain(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled,
722 domStorageEnabled, formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, ultraPrivacyEnabled, blockAllThirdPartyRequests,
723 userAgentName, fontSizeInt, swipeToRefreshInt, nightModeInt, displayWebpageImagesInt, pinnedSslCertificate, pinnedIpAddress);
725 // Update the pinned SSL certificate if a new one is checked.
726 if (currentWebsiteCertificateRadioButton.isChecked()) {
727 // Store the values from the SSL certificate.
728 String issuedToCommonName = currentSslCertificate.getIssuedTo().getCName();
729 String issuedToOrganization = currentSslCertificate.getIssuedTo().getOName();
730 String issuedToOrganizationalUnit = currentSslCertificate.getIssuedTo().getUName();
731 String issuedByCommonName = currentSslCertificate.getIssuedBy().getCName();
732 String issuedByOrganization = currentSslCertificate.getIssuedBy().getOName();
733 String issuedByOrganizationalUnit = currentSslCertificate.getIssuedBy().getUName();
734 long startDateLong = currentSslCertificate.getValidNotBeforeDate().getTime();
735 long endDateLong = currentSslCertificate.getValidNotAfterDate().getTime();
737 // Update the database.
738 domainsDatabaseHelper.updatePinnedSslCertificate(currentDomainDatabaseId, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization,
739 issuedByOrganizationalUnit, startDateLong, endDateLong);
742 // Update the pinned IP addresses if new ones are checked.
743 if (currentIpAddressesRadioButton.isChecked()) {
744 // Get the current IP addresses.
745 String currentIpAddresses = currentIpAddressesTextView.getText().toString();
747 // Update the database.
748 domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses);
752 private void populateDomainsListView(final int highlightedDomainDatabaseId) {
753 // get a handle for the current `domains_listview`.
754 domainsListView = findViewById(R.id.domains_listview);
756 // Get a `Cursor` with the current contents of the domains database.
757 Cursor domainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
759 // Setup `domainsCursorAdapter` with `this` context. `false` disables `autoRequery`.
760 CursorAdapter domainsCursorAdapter = new CursorAdapter(getApplicationContext(), domainsCursor, false) {
762 public View newView(Context context, Cursor cursor, ViewGroup parent) {
763 // Inflate the individual item layout. `false` does not attach it to the root.
764 return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
768 public void bindView(View view, Context context, Cursor cursor) {
769 // Set the domain name.
770 String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
771 TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
772 domainNameTextView.setText(domainNameString);
776 // Update the `ListView`.
777 domainsListView.setAdapter(domainsCursorAdapter);
779 // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain.
780 if (DomainsActivity.twoPanedMode && (domainsCursor.getCount() > 0)) { // Two-paned mode is enabled and there is at least one domain.
781 // Initialize `highlightedDomainPosition`.
782 int highlightedDomainPosition = 0;
784 // Get the cursor position for the highlighted domain.
785 for (int i = 0; i < domainsCursor.getCount(); i++) {
786 // Move to position `i` in the cursor.
787 domainsCursor.moveToPosition(i);
789 // Get the database ID for this position.
790 int currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
792 // Set `highlightedDomainPosition` if the database ID for this matches `highlightedDomainDatabaseId`.
793 if (highlightedDomainDatabaseId == currentDatabaseId) {
794 highlightedDomainPosition = i;
798 // Select the highlighted domain.
799 domainsListView.setItemChecked(highlightedDomainPosition, true);
801 // Get the database ID for the highlighted domain.
802 domainsCursor.moveToPosition(highlightedDomainPosition);
803 currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
805 // Store the database ID in the arguments bundle.
806 Bundle argumentsBundle = new Bundle();
807 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
809 // Add and arguments bundle to the domain settings fragment.
810 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
811 domainSettingsFragment.setArguments(argumentsBundle);
813 // Display the domain settings fragment.
814 getSupportFragmentManager().beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit();
816 // Enable the delete options menu items.
817 deleteMenuItem.setEnabled(true);
819 // Set the delete icon according to the theme.
820 if (MainWebViewActivity.darkTheme) {
821 deleteMenuItem.setIcon(R.drawable.delete_dark);
823 deleteMenuItem.setIcon(R.drawable.delete_light);
825 } else if (twoPanedMode) { // Two-paned mode is enabled but there are no domains.
826 // Disable the options `MenuItems`.
827 deleteMenuItem.setEnabled(false);
828 deleteMenuItem.setIcon(R.drawable.delete_blue);
833 public void dismissSnackbar() {
834 // Dismiss the undo delete snackbar if it is shown.
835 if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
836 // Dismiss the snackbar.
837 undoDeleteSnackbar.dismiss();
842 public void onDestroy() {
843 // Close the domains database helper.
844 domainsDatabaseHelper.close();
846 // Run the default commands.