2 * Copyright © 2017-2018 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.content.Context;
23 import android.content.Intent;
24 import android.content.res.Resources;
25 import android.database.Cursor;
26 import android.net.http.SslCertificate;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.support.design.widget.FloatingActionButton;
30 import android.support.design.widget.Snackbar;
31 import android.support.v4.app.FragmentManager;
32 import android.support.v4.app.NavUtils;
33 import android.support.v7.app.ActionBar;
34 import android.support.v7.app.AppCompatActivity;
35 // `AppCompatDialogFragment` is required instead of `DialogFragment` or an error is produced on API <=22.
36 import android.support.v7.app.AppCompatDialogFragment;
37 import android.support.v7.widget.Toolbar;
38 import android.view.Menu;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.widget.CursorAdapter;
43 import android.widget.EditText;
44 import android.widget.ListView;
45 import android.widget.RadioButton;
46 import android.widget.Spinner;
47 import android.widget.Switch;
48 import android.widget.TextView;
50 import com.stoutner.privacybrowser.R;
51 import com.stoutner.privacybrowser.dialogs.AddDomainDialog;
52 import com.stoutner.privacybrowser.fragments.DomainSettingsFragment;
53 import com.stoutner.privacybrowser.fragments.DomainsListFragment;
54 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
56 public class DomainsActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener {
57 // `twoPanedMode` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `populateDomainsListView()`.
58 public static boolean twoPanedMode;
60 // `databaseId` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onCreateOptionsMenu()`, `saveDomainSettings()` and `populateDomainsListView()`.
61 public static int currentDomainDatabaseId;
63 // `deleteMenuItem` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`.
64 public static MenuItem deleteMenuItem;
66 // `undoDeleteSnackbar` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onOptionsItemSelected()` and `onBackPressed()`.
67 public static Snackbar undoDeleteSnackbar;
69 // `dismissingSnackbar` is public static so it can be accessed from `DomainsListFragment`. It is also used in `onOptionsItemSelected()`.
70 public static boolean dismissingSnackbar;
72 // `context` is used in `onCreate()`, `onOptionsItemSelected()`, and `onAddDomain()`.
73 private Context context;
75 // `supportFragmentManager` is used in `onCreate()` and `onCreateOptionsMenu()`.
76 private FragmentManager supportFragmentManager;
78 // `domainsDatabaseHelper` is used in `onCreate()` and `saveDomainSettings()`.
79 private static DomainsDatabaseHelper domainsDatabaseHelper;
81 // `domainsListView` is used in `onCreate()` and `populateDomainsList()`.
82 private ListView domainsListView;
84 // `addDomainFAB` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`.
85 private FloatingActionButton addDomainFAB;
87 // `deletedDomainPosition` is used in an inner and outer class in `onOptionsItemSelected()`.
88 private int deletedDomainPosition;
90 // `restartAfterRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
91 private boolean restartAfterRotate;
93 // `domainSettingsDisplayedBeforeRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
94 private boolean domainSettingsDisplayedBeforeRotate;
96 // `domainSettingsDatabaseIdBeforeRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
97 private int domainSettingsDatabaseIdBeforeRotate;
99 // `goDirectlyToDatabaseId` is used in `onCreate()` and `onCreateOptionsMenu()`.
100 private int goDirectlyToDatabaseId;
102 // `closeOnBack` is used in `onCreate()`, `onOptionsItemSelected()` and `onBackPressed()`.
103 private boolean closeOnBack;
105 // `coordinatorLayout` is use in `onCreate()`, `onOptionsItemSelected()`, and `onSaveInstanceState()`.
106 private View coordinatorLayout;
108 // `resources` is used in `onCreate()`, `onOptionsItemSelected()`, and `onSaveInstanceState()`.
109 private Resources resources;
112 protected void onCreate(Bundle savedInstanceState) {
113 // Set the activity theme.
114 if (MainWebViewActivity.darkTheme) {
115 setTheme(R.style.PrivacyBrowserDark_SecondaryActivity);
117 setTheme(R.style.PrivacyBrowserLight_SecondaryActivity);
120 // Run the default commands.
121 super.onCreate(savedInstanceState);
123 // Extract the values from `savedInstanceState` if it is not `null`.
124 if (savedInstanceState != null) {
125 restartAfterRotate = true;
126 domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean("domainSettingsDisplayed");
127 domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt("domainSettingsDatabaseId");
130 // Get the launching intent
131 Intent intent = getIntent();
133 // Extract the domain to load if there is one. `-1` is the default value.
134 goDirectlyToDatabaseId = intent.getIntExtra("loadDomain", -1);
136 // Get the status of close-on-back, which is true when the domains activity is called from the options menu.
137 closeOnBack = intent.getBooleanExtra("closeOnBack", false);
139 // Set the content view.
140 setContentView(R.layout.domains_coordinatorlayout);
142 // Populate the class variables.
143 coordinatorLayout = findViewById(R.id.domains_coordinatorlayout);
144 resources = getResources();
146 supportFragmentManager = getSupportFragmentManager();
148 // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
149 final Toolbar domainsAppBar = findViewById(R.id.domains_toolbar);
150 setSupportActionBar(domainsAppBar);
152 // Display the home arrow on `SupportActionBar`.
153 ActionBar appBar = getSupportActionBar();
154 assert appBar != null;// This assert removes the incorrect warning in Android Studio on the following line that `appBar` might be null.
155 appBar.setDisplayHomeAsUpEnabled(true);
157 // Initialize the database handler. 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`.
158 domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
160 // Determine if we are in two pane mode. `domain_settings_fragment_container` does not exist on devices with a width less than 900dp.
161 twoPanedMode = (findViewById(R.id.domain_settings_fragment_container) != null);
163 // Configure `addDomainFAB`.
164 addDomainFAB = findViewById(R.id.add_domain_fab);
165 addDomainFAB.setOnClickListener((View view) -> {
166 // Show the add domain `AlertDialog`.
167 AppCompatDialogFragment addDomainDialog = new AddDomainDialog();
168 addDomainDialog.show(supportFragmentManager, resources.getString(R.string.add_domain));
173 public boolean onCreateOptionsMenu(Menu menu) {
175 getMenuInflater().inflate(R.menu.domains_options_menu, menu);
177 // Store `deleteMenuItem` for future use.
178 deleteMenuItem = menu.findItem(R.id.delete_domain);
180 // Only display `deleteMenuItem` (initially) in two-paned mode.
181 deleteMenuItem.setVisible(twoPanedMode);
183 // Display the fragments. This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated.
184 if (restartAfterRotate && domainSettingsDisplayedBeforeRotate) { // The device was rotated and domain settings were displayed previously.
185 if (twoPanedMode) { // The device is in two-paned mode.
186 // Reset `restartAfterRotate`.
187 restartAfterRotate = false;
189 // Display `DomainsListFragment`.
190 DomainsListFragment domainsListFragment = new DomainsListFragment();
191 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
192 supportFragmentManager.executePendingTransactions();
194 // Populate the list of domains. `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation.
195 populateDomainsListView(domainSettingsDatabaseIdBeforeRotate);
196 } else { // The device is in single-paned mode.
197 // Reset `restartAfterRotate`.
198 restartAfterRotate = false;
200 // Store the current domain database ID.
201 currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRotate;
203 // Add `currentDomainDatabaseId` to `argumentsBundle`.
204 Bundle argumentsBundle = new Bundle();
205 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
207 // Add `argumentsBundle` to `domainSettingsFragment`.
208 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
209 domainSettingsFragment.setArguments(argumentsBundle);
211 // Show `deleteMenuItem`.
212 deleteMenuItem.setVisible(true);
214 // Hide `add_domain_fab`.
215 addDomainFAB.setVisibility(View.GONE);
217 // Display `domainSettingsFragment`.
218 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
220 } else { // The device was not rotated or, if it was, domain settings were not displayed previously.
221 if (goDirectlyToDatabaseId >=0) { // Load the indicated domain settings.
222 // Store the current domain database ID.
223 currentDomainDatabaseId = goDirectlyToDatabaseId;
225 if (twoPanedMode) { // The device is in two-paned mode.
226 // Display `DomainsListFragment`.
227 DomainsListFragment domainsListFragment = new DomainsListFragment();
228 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
229 supportFragmentManager.executePendingTransactions();
231 // Populate the list of domains. `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation.
232 populateDomainsListView(goDirectlyToDatabaseId);
233 } else { // The device is in single-paned mode.
234 // Add the domain ID to be loaded to `argumentsBundle`.
235 Bundle argumentsBundle = new Bundle();
236 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId);
238 // Add `argumentsBundle` to `domainSettingsFragment`.
239 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
240 domainSettingsFragment.setArguments(argumentsBundle);
242 // Show `deleteMenuItem`.
243 deleteMenuItem.setVisible(true);
245 // Hide `add_domain_fab`.
246 addDomainFAB.setVisibility(View.GONE);
248 // Display `domainSettingsFragment`.
249 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
251 } else { // Highlight the first domain.
252 // Display `DomainsListFragment`.
253 DomainsListFragment domainsListFragment = new DomainsListFragment();
254 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
255 supportFragmentManager.executePendingTransactions();
257 // Populate the list of domains. `-1` highlights the first domain.
258 populateDomainsListView(-1);
267 public boolean onOptionsItemSelected(MenuItem menuItem) {
268 // Get the ID of the `MenuItem` that was selected.
269 int menuItemID = menuItem.getItemId();
271 switch (menuItemID) {
272 case android.R.id.home: // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
273 if (twoPanedMode) { // The device is in two-paned mode.
274 // Save the current domain settings if the domain settings fragment is displayed.
275 if (findViewById(R.id.domain_settings_scrollview) != null) {
276 saveDomainSettings(coordinatorLayout, resources);
279 // Dismiss the undo delete `SnackBar` if it is shown.
280 if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
281 undoDeleteSnackbar.dismiss();
283 // Create a `Runnable` to return to the main activity.
284 Runnable navigateHomeRunnable = () -> {
286 NavUtils.navigateUpFromSameTask(this);
289 // Navigate home after 300 milliseconds to make sure that the previous domain has been deleted from the database.
290 Handler handler = new Handler();
291 handler.postDelayed(navigateHomeRunnable, 300);
294 NavUtils.navigateUpFromSameTask(this);
296 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
297 // Save the current domain settings.
298 saveDomainSettings(coordinatorLayout, resources);
301 NavUtils.navigateUpFromSameTask(this);
302 } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed.
303 // Save the current domain settings.
304 saveDomainSettings(coordinatorLayout, resources);
306 // Display the domains list fragment.
307 DomainsListFragment domainsListFragment = new DomainsListFragment();
308 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
309 supportFragmentManager.executePendingTransactions();
311 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
312 populateDomainsListView(-1);
314 // Display the add domain FAB.
315 addDomainFAB.setVisibility(View.VISIBLE);
317 // Hide the delete menu item.
318 deleteMenuItem.setVisible(false);
319 } else { // The device is in single-paned mode and `DomainsListFragment` is displayed.
320 // Dismiss the undo delete `SnackBar` if it is shown.
321 if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
322 undoDeleteSnackbar.dismiss();
324 // Create a `Runnable` to return to the main activity.
325 Runnable navigateHomeRunnable = () -> {
327 NavUtils.navigateUpFromSameTask(this);
330 // Navigate home after 300 milliseconds to make sure that the previous domain has been deleted from the database.
331 Handler handler = new Handler();
332 handler.postDelayed(navigateHomeRunnable, 300);
335 NavUtils.navigateUpFromSameTask(this);
340 case R.id.delete_domain:
341 // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists.
344 // Store a copy of `currentDomainDatabaseId` because it could change while the `Snackbar` is displayed.
345 final int databaseIdToDelete = currentDomainDatabaseId;
347 // Update the fragments and menu items.
348 if (twoPanedMode) { // Two-paned mode.
349 // Store the deleted domain position, which is needed if `Undo` is selected in the `Snackbar`.
350 deletedDomainPosition = domainsListView.getCheckedItemPosition();
352 // Disable the options `MenuItems`.
353 deleteMenuItem.setEnabled(false);
354 deleteMenuItem.setIcon(R.drawable.delete_blue);
356 // Remove the domain settings fragment.
357 supportFragmentManager.beginTransaction().remove(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)).commit();
358 } else { // Single-paned mode.
359 // Display `DomainsListFragment`.
360 DomainsListFragment domainsListFragment = new DomainsListFragment();
361 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
362 supportFragmentManager.executePendingTransactions();
364 // Display `addDomainFAB`.
365 addDomainFAB.setVisibility(View.VISIBLE);
367 // Hide `deleteMenuItem`.
368 deleteMenuItem.setVisible(false);
371 // Get a `Cursor` that does not show the domain to be deleted.
372 Cursor domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete);
374 // Setup `domainsPendingDeleteCursorAdapter` with `this` context. `false` disables `autoRequery`.
375 CursorAdapter domainsPendingDeleteCursorAdapter = new CursorAdapter(this, domainsPendingDeleteCursor, false) {
377 public View newView(Context context, Cursor cursor, ViewGroup parent) {
378 // Inflate the individual item layout. `false` does not attach it to the root.
379 return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
383 public void bindView(View view, Context context, Cursor cursor) {
384 // Set the domain name.
385 String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
386 TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
387 domainNameTextView.setText(domainNameString);
391 // Update the handle for the current `domains_listview`.
392 domainsListView = findViewById(R.id.domains_listview);
394 // Update the `ListView`.
395 domainsListView.setAdapter(domainsPendingDeleteCursorAdapter);
397 // Display a `Snackbar`.
398 undoDeleteSnackbar = Snackbar.make(domainsListView, R.string.domain_deleted, Snackbar.LENGTH_LONG)
399 .setAction(R.string.undo, (View v) -> {
400 // Do nothing because everything will be handled by `onDismissed()` below.
402 .addCallback(new Snackbar.Callback() {
404 public void onDismissed(Snackbar snackbar, int event) {
406 // The user pushed the `Undo` button.
407 case Snackbar.Callback.DISMISS_EVENT_ACTION:
408 // Store `databaseId` in `argumentsBundle`.
409 Bundle argumentsBundle = new Bundle();
410 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete);
412 // Add `argumentsBundle` to `domainSettingsFragment`.
413 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
414 domainSettingsFragment.setArguments(argumentsBundle);
416 // Display the correct fragments.
417 if (twoPanedMode) { // The device in in two-paned mode.
418 // Get a `Cursor` with the current contents of the domains database.
419 Cursor undoDeleteDomainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
421 // Setup `domainsCursorAdapter` with `this` context. `false` disables `autoRequery`.
422 CursorAdapter undoDeleteDomainsCursorAdapter = new CursorAdapter(context, undoDeleteDomainsCursor, false) {
424 public View newView(Context context, Cursor cursor, ViewGroup parent) {
425 // Inflate the individual item layout. `false` does not attach it to the root.
426 return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
430 public void bindView(View view, Context context, Cursor cursor) {
431 // Set the domain name.
432 String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
433 TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
434 domainNameTextView.setText(domainNameString);
438 // Update the `ListView`.
439 domainsListView.setAdapter(undoDeleteDomainsCursorAdapter);
440 // Select the previously deleted domain in `domainsListView`.
441 domainsListView.setItemChecked(deletedDomainPosition, true);
443 // Display `domainSettingsFragment`.
444 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit();
446 // Enable the options `MenuItems`.
447 deleteMenuItem.setEnabled(true);
448 deleteMenuItem.setIcon(R.drawable.delete_light);
449 } else { // The device in in one-paned mode.
450 // Display `domainSettingsFragment`.
451 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
453 // Hide `add_domain_fab`.
454 FloatingActionButton addDomainFAB = findViewById(R.id.add_domain_fab);
455 addDomainFAB.setVisibility(View.GONE);
457 // Show and enable `deleteMenuItem`.
458 deleteMenuItem.setVisible(true);
460 // Display `domainSettingsFragment`.
461 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
465 // The `Snackbar` was dismissed without the `Undo` button being pushed.
467 // Delete the selected domain.
468 domainsDatabaseHelper.deleteDomain(databaseIdToDelete);
470 // enable `deleteMenuItem` if the system was waiting for a `Snackbar` to be dismissed.
471 if (dismissingSnackbar) {
472 // Create a `Runnable` to enable the delete menu item.
473 Runnable enableDeleteMenuItemRunnable = () -> {
474 // Enable `deleteMenuItem` according to the display mode.
475 if (twoPanedMode) { // Two-paned mode.
476 // Enable `deleteMenuItem`.
477 deleteMenuItem.setEnabled(true);
479 // Set the delete icon according to the theme.
480 if (MainWebViewActivity.darkTheme) {
481 deleteMenuItem.setIcon(R.drawable.delete_dark);
483 deleteMenuItem.setIcon(R.drawable.delete_light);
485 } else { // Single-paned mode.
486 // Show `deleteMenuItem`.
487 deleteMenuItem.setVisible(true);
490 // Reset `dismissingSnackbar`.
491 dismissingSnackbar = false;
494 // Run `enableDeleteMenuItemRunnable` after 100 milliseconds to make sure that the previous domain has been deleted from the database.
495 Handler handler = new Handler();
496 handler.postDelayed(enableDeleteMenuItemRunnable, 100);
502 undoDeleteSnackbar.show();
506 // Consume the event.
511 protected void onSaveInstanceState(Bundle outState) {
512 // Store the current `DomainSettingsFragment` state in `outState`.
513 if (findViewById(R.id.domain_settings_scrollview) != null) { // `DomainSettingsFragment` is displayed.
514 // Save any changes that have been made to the domain settings.
515 saveDomainSettings(coordinatorLayout, resources);
517 // Store `DomainSettingsDisplayed`.
518 outState.putBoolean("domainSettingsDisplayed", true);
519 outState.putInt("domainSettingsDatabaseId", DomainSettingsFragment.databaseId);
520 } else { // `DomainSettingsFragment` is not displayed.
521 outState.putBoolean("domainSettingsDisplayed", false);
522 outState.putInt("domainSettingsDatabaseId", -1);
525 super.onSaveInstanceState(outState);
528 // Control what the navigation bar back button does.
530 public void onBackPressed() {
531 if (twoPanedMode) { // The device is in two-paned mode.
532 // Save the current domain settings if the domain settings fragment is displayed.
533 if (findViewById(R.id.domain_settings_scrollview) != null) {
534 saveDomainSettings(coordinatorLayout, resources);
537 // Dismiss the undo delete SnackBar if it is shown.
538 if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
539 undoDeleteSnackbar.dismiss();
541 // Create a runnable to return to the main activity.
542 Runnable navigateHomeRunnable = super::onBackPressed;
544 // Navigate home after 300 milliseconds to make sure that the previous domain has been deleted from the database.
545 Handler handler = new Handler();
546 handler.postDelayed(navigateHomeRunnable, 300);
548 // Pass `onBackPressed()` to the system.
549 super.onBackPressed();
551 } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
552 // Save the current domain settings.
553 saveDomainSettings(coordinatorLayout, resources);
555 // Pass `onBackPressed()` to the system.
556 super.onBackPressed();
557 } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and domain settings fragment is displayed.
558 // Save the current domain settings.
559 saveDomainSettings(coordinatorLayout, resources);
561 // Display the domains list fragment.
562 DomainsListFragment domainsListFragment = new DomainsListFragment();
563 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
564 supportFragmentManager.executePendingTransactions();
566 // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode.
567 populateDomainsListView(-1);
569 // Display the add domain FAB.
570 addDomainFAB.setVisibility(View.VISIBLE);
572 // Hide the delete menu item.
573 deleteMenuItem.setVisible(false);
574 } else { // The device is in single-paned mode and the domain list fragment is displayed.
575 // Dismiss the undo delete SnackBar if it is shown.
576 if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
577 undoDeleteSnackbar.dismiss();
579 // Create a runnable to return to the main activity.
580 Runnable navigateHomeRunnable = super::onBackPressed;
582 // Navigate home after 300 milliseconds to make sure that the previous domain has been deleted from the database.
583 Handler handler = new Handler();
584 handler.postDelayed(navigateHomeRunnable, 300);
586 // Pass `onBackPressed()` to the system.
587 super.onBackPressed();
593 public void onAddDomain(AppCompatDialogFragment dialogFragment) {
594 // Dismiss the undo delete snackbar if it is currently displayed.
595 if ((undoDeleteSnackbar != null) && (undoDeleteSnackbar.isShown())) {
596 undoDeleteSnackbar.dismiss();
599 // Get the new domain name String from the dialog fragment.
600 EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
601 String domainNameString = domainNameEditText.getText().toString();
603 // Create the domain and store the database ID in `currentDomainDatabaseId`.
604 currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString);
606 // Display the newly created domain.
607 if (twoPanedMode) { // The device in in two-paned mode.
608 populateDomainsListView(currentDomainDatabaseId);
609 } else { // The device is in single-paned mode.
610 // Hide `add_domain_fab`.
611 addDomainFAB.setVisibility(View.GONE);
613 // Show and enable `deleteMenuItem`.
614 DomainsActivity.deleteMenuItem.setVisible(true);
616 // Add `currentDomainDatabaseId` to `argumentsBundle`.
617 Bundle argumentsBundle = new Bundle();
618 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
620 // Add `argumentsBundle` to `domainSettingsFragment`.
621 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
622 domainSettingsFragment.setArguments(argumentsBundle);
624 // Display `domainSettingsFragment`.
625 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
629 public void saveDomainSettings(View view, Resources resources) {
630 // Get handles for the domain settings.
631 EditText domainNameEditText = view.findViewById(R.id.domain_settings_name_edittext);
632 Switch javaScriptSwitch = view.findViewById(R.id.domain_settings_javascript_switch);
633 Switch firstPartyCookiesSwitch = view.findViewById(R.id.domain_settings_first_party_cookies_switch);
634 Switch thirdPartyCookiesSwitch = view.findViewById(R.id.domain_settings_third_party_cookies_switch);
635 Switch domStorageSwitch = view.findViewById(R.id.domain_settings_dom_storage_switch);
636 Switch formDataSwitch = view.findViewById(R.id.domain_settings_form_data_switch);
637 Switch easyListSwitch = view.findViewById(R.id.domain_settings_easylist_switch);
638 Switch easyPrivacySwitch = view.findViewById(R.id.domain_settings_easyprivacy_switch);
639 Switch fanboysAnnoyanceSwitch = view.findViewById(R.id.domain_settings_fanboys_annoyance_list_switch);
640 Switch fanboysSocialBlockingSwitch = view.findViewById(R.id.domain_settings_fanboys_social_blocking_list_switch);
641 Spinner userAgentSpinner = view.findViewById(R.id.domain_settings_user_agent_spinner);
642 EditText customUserAgentEditText = view.findViewById(R.id.domain_settings_custom_user_agent_edittext);
643 Spinner fontSizeSpinner = view.findViewById(R.id.domain_settings_font_size_spinner);
644 Spinner displayWebpageImagesSpinner = view.findViewById(R.id.domain_settings_display_webpage_images_spinner);
645 Spinner nightModeSpinner = view.findViewById(R.id.domain_settings_night_mode_spinner);
646 Switch pinnedSslCertificateSwitch = view.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
647 RadioButton savedSslCertificateRadioButton = view.findViewById(R.id.saved_ssl_certificate_radiobutton);
648 RadioButton currentWebsiteCertificateRadioButton = view.findViewById(R.id.current_website_certificate_radiobutton);
650 // Extract the data for the domain settings.
651 String domainNameString = domainNameEditText.getText().toString();
652 boolean javaScriptEnabled = javaScriptSwitch.isChecked();
653 boolean firstPartyCookiesEnabled = firstPartyCookiesSwitch.isChecked();
654 boolean thirdPartyCookiesEnabled = thirdPartyCookiesSwitch.isChecked();
655 boolean domStorageEnabled = domStorageSwitch.isChecked();
656 boolean formDataEnabled = formDataSwitch.isChecked();
657 boolean easyListEnabled = easyListSwitch.isChecked();
658 boolean easyPrivacyEnabled = easyPrivacySwitch.isChecked();
659 boolean fanboysAnnoyanceEnabled = fanboysAnnoyanceSwitch.isChecked();
660 boolean fanboysSocialBlockingEnabled = fanboysSocialBlockingSwitch.isChecked();
661 int userAgentPosition = userAgentSpinner.getSelectedItemPosition();
662 int fontSizePosition = fontSizeSpinner.getSelectedItemPosition();
663 int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
664 int nightModeInt = nightModeSpinner.getSelectedItemPosition();
665 boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
667 // Initialize the user agent name string.
668 String userAgentName;
670 // Set the user agent name.
671 switch (userAgentPosition) {
672 case MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT:
673 // Set the user agent name to be `System default user agent`.
674 userAgentName = resources.getString(R.string.system_default_user_agent);
677 case MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT:
678 // Set the user agent name to be the custom user agent.
679 userAgentName = customUserAgentEditText.getText().toString();
683 // Get the array of user agent names.
684 String[] userAgentNameArray = resources.getStringArray(R.array.user_agent_names);
686 // 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.
687 userAgentName = userAgentNameArray[userAgentPosition - 1];
690 // Get the font size integer.
691 int fontSizeInt = Integer.parseInt(resources.getStringArray(R.array.domain_settings_font_size_entry_values)[fontSizePosition]);
693 // Save the domain settings.
694 if (savedSslCertificateRadioButton.isChecked()) { // The current certificate is being used.
695 // Update the database except for the certificate.
696 domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled,
697 domStorageEnabled, formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, userAgentName, fontSizeInt, displayWebpageImagesInt,
698 nightModeInt, pinnedSslCertificate);
699 } else if (currentWebsiteCertificateRadioButton.isChecked()) { // The certificate is being updated with the current website certificate.
700 // Get the current website SSL certificate.
701 SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
703 // Store the values from the SSL certificate.
704 String issuedToCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName();
705 String issuedToOrganization = currentWebsiteSslCertificate.getIssuedTo().getOName();
706 String issuedToOrganizationalUnit = currentWebsiteSslCertificate.getIssuedTo().getUName();
707 String issuedByCommonName = currentWebsiteSslCertificate.getIssuedBy().getCName();
708 String issuedByOrganization = currentWebsiteSslCertificate.getIssuedBy().getOName();
709 String issuedByOrganizationalUnit = currentWebsiteSslCertificate.getIssuedBy().getUName();
710 long startDateLong = currentWebsiteSslCertificate.getValidNotBeforeDate().getTime();
711 long endDateLong = currentWebsiteSslCertificate.getValidNotAfterDate().getTime();
713 // Update the database.
714 domainsDatabaseHelper.updateDomainWithCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled, domStorageEnabled,
715 formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, userAgentName, fontSizeInt, displayWebpageImagesInt, nightModeInt,
716 pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization, issuedByOrganizationalUnit, startDateLong, endDateLong);
718 } else { // No certificate is selected.
719 // Update the database, with PINNED_SSL_CERTIFICATE set to false.
720 domainsDatabaseHelper.updateDomainExceptCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled, domStorageEnabled,
721 formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, userAgentName, fontSizeInt, displayWebpageImagesInt, nightModeInt,
726 private void populateDomainsListView(final int highlightedDomainDatabaseId) {
727 // get a handle for the current `domains_listview`.
728 domainsListView = findViewById(R.id.domains_listview);
730 // Get a `Cursor` with the current contents of the domains database.
731 Cursor domainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
733 // Setup `domainsCursorAdapter` with `this` context. `false` disables `autoRequery`.
734 CursorAdapter domainsCursorAdapter = new CursorAdapter(context, domainsCursor, false) {
736 public View newView(Context context, Cursor cursor, ViewGroup parent) {
737 // Inflate the individual item layout. `false` does not attach it to the root.
738 return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
742 public void bindView(View view, Context context, Cursor cursor) {
743 // Set the domain name.
744 String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
745 TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
746 domainNameTextView.setText(domainNameString);
750 // Update the `ListView`.
751 domainsListView.setAdapter(domainsCursorAdapter);
753 // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain.
754 if (DomainsActivity.twoPanedMode && (domainsCursor.getCount() > 0)) { // Two-paned mode is enabled and there is at least one domain.
755 // Initialize `highlightedDomainPosition`.
756 int highlightedDomainPosition = 0;
758 // Get the cursor position for the highlighted domain.
759 for (int i = 0; i < domainsCursor.getCount(); i++) {
760 // Move to position `i` in the cursor.
761 domainsCursor.moveToPosition(i);
763 // Get the database ID for this position.
764 int currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
766 // Set `highlightedDomainPosition` if the database ID for this matches `highlightedDomainDatabaseId`.
767 if (highlightedDomainDatabaseId == currentDatabaseId) {
768 highlightedDomainPosition = i;
772 // Select the highlighted domain.
773 domainsListView.setItemChecked(highlightedDomainPosition, true);
775 // Get the `databaseId` for the highlighted domain.
776 domainsCursor.moveToPosition(highlightedDomainPosition);
777 currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
779 // Store `databaseId` in `argumentsBundle`.
780 Bundle argumentsBundle = new Bundle();
781 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
783 // Add `argumentsBundle` to `domainSettingsFragment`.
784 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
785 domainSettingsFragment.setArguments(argumentsBundle);
787 // Display `domainSettingsFragment`.
788 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit();
790 // Enable the options `MenuItems`.
791 deleteMenuItem.setEnabled(true);
793 // Set the delete icon according to the theme.
794 if (MainWebViewActivity.darkTheme) {
795 deleteMenuItem.setIcon(R.drawable.delete_dark);
797 deleteMenuItem.setIcon(R.drawable.delete_light);
799 } else if (twoPanedMode) { // Two-paned mode is enabled but there are no domains.
800 // Disable the options `MenuItems`.
801 deleteMenuItem.setEnabled(false);
802 deleteMenuItem.setIcon(R.drawable.delete_blue);