]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
1d9a62a6aadd858273001888b6c064a0c80315fa
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / DomainsActivity.java
1 /*
2  * Copyright © 2017-2018 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.activities;
21
22 import android.app.Activity;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.res.Resources;
26 import android.database.Cursor;
27 import android.net.http.SslCertificate;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.support.design.widget.FloatingActionButton;
31 import android.support.design.widget.Snackbar;
32 import android.support.v4.app.FragmentManager;
33 import android.support.v4.app.NavUtils;
34 import android.support.v7.app.ActionBar;
35 import android.support.v7.app.AppCompatActivity;
36 // `AppCompatDialogFragment` is required instead of `DialogFragment` or an error is produced on API <=22.
37 import android.support.v7.app.AppCompatDialogFragment;
38 import android.support.v7.widget.Toolbar;
39 import android.view.Menu;
40 import android.view.MenuItem;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.view.WindowManager;
44 import android.widget.CursorAdapter;
45 import android.widget.EditText;
46 import android.widget.ListView;
47 import android.widget.RadioButton;
48 import android.widget.Spinner;
49 import android.widget.Switch;
50 import android.widget.TextView;
51
52 import com.stoutner.privacybrowser.R;
53 import com.stoutner.privacybrowser.dialogs.AddDomainDialog;
54 import com.stoutner.privacybrowser.fragments.DomainSettingsFragment;
55 import com.stoutner.privacybrowser.fragments.DomainsListFragment;
56 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
57
58 import java.util.Objects;
59
60 public class DomainsActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener {
61     // `twoPanedMode` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `populateDomainsListView()`.
62     public static boolean twoPanedMode;
63
64     // `databaseId` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onCreateOptionsMenu()`, `saveDomainSettings()` and `populateDomainsListView()`.
65     public static int currentDomainDatabaseId;
66
67     // `deleteMenuItem` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`.
68     public static MenuItem deleteMenuItem;
69
70     // `undoDeleteSnackbar` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onOptionsItemSelected()` and `onBackPressed()`.
71     public static Snackbar undoDeleteSnackbar;
72
73     // `dismissingSnackbar` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onOptionsItemSelected()`.
74     public static boolean dismissingSnackbar;
75
76     // `closeActivityAfterDismissingSnackbar` is used in `onOptionsItemSelected()`, and `onBackPressed()`.
77     private boolean closeActivityAfterDismissingSnackbar;
78
79     // `context` is used in `onCreate()`, `onOptionsItemSelected()`, and `onAddDomain()`.
80     private Context context;
81
82     // `supportFragmentManager` is used in `onCreate()` and `onCreateOptionsMenu()`.
83     private FragmentManager supportFragmentManager;
84
85     // `domainsDatabaseHelper` is used in `onCreate()`, `saveDomainSettings()`, and `onDestroy()`.
86     private static DomainsDatabaseHelper domainsDatabaseHelper;
87
88     // `domainsListView` is used in `onCreate()` and `populateDomainsList()`.
89     private ListView domainsListView;
90
91     // `addDomainFAB` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`.
92     private FloatingActionButton addDomainFAB;
93
94     // `deletedDomainPosition` is used in an inner and outer class in `onOptionsItemSelected()`.
95     private int deletedDomainPosition;
96
97     // `restartAfterRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
98     private boolean restartAfterRotate;
99
100     // `domainSettingsDisplayedBeforeRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
101     private boolean domainSettingsDisplayedBeforeRotate;
102
103     // `domainSettingsDatabaseIdBeforeRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
104     private int domainSettingsDatabaseIdBeforeRotate;
105
106     // `goDirectlyToDatabaseId` is used in `onCreate()` and `onCreateOptionsMenu()`.
107     private int goDirectlyToDatabaseId;
108
109     // `closeOnBack` is used in `onCreate()`, `onOptionsItemSelected()` and `onBackPressed()`.
110     private boolean closeOnBack;
111
112     // `coordinatorLayout` is use in `onCreate()`, `onOptionsItemSelected()`, and `onSaveInstanceState()`.
113     private View coordinatorLayout;
114
115     // `resources` is used in `onCreate()`, `onOptionsItemSelected()`, and `onSaveInstanceState()`.
116     private Resources resources;
117
118     @Override
119     protected void onCreate(Bundle savedInstanceState) {
120         // Disable screenshots if not allowed.
121         if (!MainWebViewActivity.allowScreenshots) {
122             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
123         }
124
125         // Set the activity theme.
126         if (MainWebViewActivity.darkTheme) {
127             setTheme(R.style.PrivacyBrowserDark_SecondaryActivity);
128         } else {
129             setTheme(R.style.PrivacyBrowserLight_SecondaryActivity);
130         }
131
132         // Run the default commands.
133         super.onCreate(savedInstanceState);
134
135         // Extract the values from `savedInstanceState` if it is not `null`.
136         if (savedInstanceState != null) {
137             restartAfterRotate = true;
138             domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean("domainSettingsDisplayed");
139             domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt("domainSettingsDatabaseId");
140         }
141
142         // Get the launching intent
143         Intent intent = getIntent();
144
145         // Extract the domain to load if there is one.  `-1` is the default value.
146         goDirectlyToDatabaseId = intent.getIntExtra("loadDomain", -1);
147
148         // Get the status of close-on-back, which is true when the domains activity is called from the options menu.
149         closeOnBack = intent.getBooleanExtra("closeOnBack", false);
150
151         // Set the content view.
152         setContentView(R.layout.domains_coordinatorlayout);
153
154         // Populate the class variables.
155         coordinatorLayout = findViewById(R.id.domains_coordinatorlayout);
156         resources = getResources();
157         context = this;
158         supportFragmentManager = getSupportFragmentManager();
159
160         // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
161         final Toolbar domainsAppBar = findViewById(R.id.domains_toolbar);
162         setSupportActionBar(domainsAppBar);
163
164         // Display the home arrow on the support action bar.
165         ActionBar appBar = getSupportActionBar();
166         assert appBar != null;// This assert removes the incorrect warning in Android Studio on the following line that `appBar` might be null.
167         appBar.setDisplayHomeAsUpEnabled(true);
168
169         // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
170         domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
171
172         // Determine if we are in two pane mode.  `domain_settings_fragment_container` does not exist on devices with a width less than 900dp.
173         twoPanedMode = (findViewById(R.id.domain_settings_fragment_container) != null);
174
175         // Configure `addDomainFAB`.
176         addDomainFAB = findViewById(R.id.add_domain_fab);
177         addDomainFAB.setOnClickListener((View view) -> {
178             // Show the add domain `AlertDialog`.
179             AppCompatDialogFragment addDomainDialog = new AddDomainDialog();
180             addDomainDialog.show(supportFragmentManager, resources.getString(R.string.add_domain));
181         });
182     }
183
184     @Override
185     public boolean onCreateOptionsMenu(Menu menu) {
186         // Inflate the menu.
187         getMenuInflater().inflate(R.menu.domains_options_menu, menu);
188
189         // Store `deleteMenuItem` for future use.
190         deleteMenuItem = menu.findItem(R.id.delete_domain);
191
192         // Only display `deleteMenuItem` (initially) in two-paned mode.
193         deleteMenuItem.setVisible(twoPanedMode);
194
195         // Display the fragments.  This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated.
196         if (restartAfterRotate && domainSettingsDisplayedBeforeRotate) {  // The device was rotated and domain settings were displayed previously.
197             if (twoPanedMode) {  // The device is in two-paned mode.
198                 // Reset `restartAfterRotate`.
199                 restartAfterRotate = false;
200
201                 // Display `DomainsListFragment`.
202                 DomainsListFragment domainsListFragment = new DomainsListFragment();
203                 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
204                 supportFragmentManager.executePendingTransactions();
205
206                 // Populate the list of domains.  `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation.
207                 populateDomainsListView(domainSettingsDatabaseIdBeforeRotate);
208             } else {  // The device is in single-paned mode.
209                 // Reset `restartAfterRotate`.
210                 restartAfterRotate = false;
211
212                 // Store the current domain database ID.
213                 currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRotate;
214
215                 // Add `currentDomainDatabaseId` to `argumentsBundle`.
216                 Bundle argumentsBundle = new Bundle();
217                 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
218
219                 // Add `argumentsBundle` to `domainSettingsFragment`.
220                 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
221                 domainSettingsFragment.setArguments(argumentsBundle);
222
223                 // Show `deleteMenuItem`.
224                 deleteMenuItem.setVisible(true);
225
226                 // Hide `add_domain_fab`.
227                 addDomainFAB.setVisibility(View.GONE);
228
229                 // Display `domainSettingsFragment`.
230                 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
231             }
232         } else {  // The device was not rotated or, if it was, domain settings were not displayed previously.
233             if (goDirectlyToDatabaseId >=0) {  // Load the indicated domain settings.
234                 // Store the current domain database ID.
235                 currentDomainDatabaseId = goDirectlyToDatabaseId;
236
237                 if (twoPanedMode) {  // The device is in two-paned mode.
238                     // Display `DomainsListFragment`.
239                     DomainsListFragment domainsListFragment = new DomainsListFragment();
240                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
241                     supportFragmentManager.executePendingTransactions();
242
243                     // Populate the list of domains.  `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation.
244                     populateDomainsListView(goDirectlyToDatabaseId);
245                 } else {  // The device is in single-paned mode.
246                     // Add the domain ID to be loaded to `argumentsBundle`.
247                     Bundle argumentsBundle = new Bundle();
248                     argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId);
249
250                     // Add `argumentsBundle` to `domainSettingsFragment`.
251                     DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
252                     domainSettingsFragment.setArguments(argumentsBundle);
253
254                     // Show `deleteMenuItem`.
255                     deleteMenuItem.setVisible(true);
256
257                     // Hide `add_domain_fab`.
258                     addDomainFAB.setVisibility(View.GONE);
259
260                     // Display `domainSettingsFragment`.
261                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
262                 }
263             } else {  // Highlight the first domain.
264                 // Display `DomainsListFragment`.
265                 DomainsListFragment domainsListFragment = new DomainsListFragment();
266                 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
267                 supportFragmentManager.executePendingTransactions();
268
269                 // Populate the list of domains.  `-1` highlights the first domain.
270                 populateDomainsListView(-1);
271             }
272         }
273
274         // Success!
275         return true;
276     }
277
278     @Override
279     public boolean onOptionsItemSelected(MenuItem menuItem) {
280         // Get the ID of the `MenuItem` that was selected.
281         int menuItemID = menuItem.getItemId();
282
283         switch (menuItemID) {
284             case android.R.id.home:  // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
285                 if (twoPanedMode) {  // The device is in two-paned mode.
286                     // Save the current domain settings if the domain settings fragment is displayed.
287                     if (findViewById(R.id.domain_settings_scrollview) != null) {
288                         saveDomainSettings(coordinatorLayout, resources);
289                     }
290
291                     // Dismiss the undo delete `SnackBar` if it is shown.
292                     if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
293                         // Set the close flag.
294                         closeActivityAfterDismissingSnackbar = true;
295
296                         // Dismiss the snackbar.
297                         undoDeleteSnackbar.dismiss();
298                     } else {
299                         // Go home.
300                         NavUtils.navigateUpFromSameTask(this);
301                     }
302                 } else if (closeOnBack) {  // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
303                     // Save the current domain settings.
304                     saveDomainSettings(coordinatorLayout, resources);
305
306                     // Go home.
307                     NavUtils.navigateUpFromSameTask(this);
308                 } else if (findViewById(R.id.domain_settings_scrollview) != null) {  // The device is in single-paned mode and the domain settings fragment is displayed.
309                     // Save the current domain settings.
310                     saveDomainSettings(coordinatorLayout, resources);
311
312                     // Display the domains list fragment.
313                     DomainsListFragment domainsListFragment = new DomainsListFragment();
314                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
315                     supportFragmentManager.executePendingTransactions();
316
317                     // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
318                     populateDomainsListView(-1);
319
320                     // Display the add domain FAB.
321                     addDomainFAB.setVisibility(View.VISIBLE);
322
323                     // Hide the delete menu item.
324                     deleteMenuItem.setVisible(false);
325                 } else {  // The device is in single-paned mode and `DomainsListFragment` is displayed.
326                     // Dismiss the undo delete `SnackBar` if it is shown.
327                     if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
328                         // Set the close flag.
329                         closeActivityAfterDismissingSnackbar = true;
330
331                         // Dismiss the snackbar.
332                         undoDeleteSnackbar.dismiss();
333                     } else {
334                         // Go home.
335                         NavUtils.navigateUpFromSameTask(this);
336                     }
337                 }
338                 break;
339
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.
342                 closeOnBack = false;
343
344                 // Store a copy of `currentDomainDatabaseId` because it could change while the `Snackbar` is displayed.
345                 final int databaseIdToDelete = currentDomainDatabaseId;
346
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();
351
352                     // Disable the options `MenuItems`.
353                     deleteMenuItem.setEnabled(false);
354                     deleteMenuItem.setIcon(R.drawable.delete_blue);
355
356                     // Remove the domain settings fragment.
357                     supportFragmentManager.beginTransaction().remove(Objects.requireNonNull(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();
363
364                     // Display `addDomainFAB`.
365                     addDomainFAB.setVisibility(View.VISIBLE);
366
367                     // Hide `deleteMenuItem`.
368                     deleteMenuItem.setVisible(false);
369                 }
370
371                 // Get a `Cursor` that does not show the domain to be deleted.
372                 Cursor domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete);
373
374                 // Setup `domainsPendingDeleteCursorAdapter` with `this` context.  `false` disables `autoRequery`.
375                 CursorAdapter domainsPendingDeleteCursorAdapter = new CursorAdapter(this, domainsPendingDeleteCursor, false) {
376                     @Override
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);
380                     }
381
382                     @Override
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);
388                     }
389                 };
390
391                 // Update the handle for the current `domains_listview`.
392                 domainsListView = findViewById(R.id.domains_listview);
393
394                 // Update the `ListView`.
395                 domainsListView.setAdapter(domainsPendingDeleteCursorAdapter);
396
397                 // Get a handle for the activity.
398                 Activity activity = this;
399
400                 // Display a `Snackbar`.
401                 undoDeleteSnackbar = Snackbar.make(domainsListView, R.string.domain_deleted, Snackbar.LENGTH_LONG)
402                         .setAction(R.string.undo, (View v) -> {
403                             // Do nothing because everything will be handled by `onDismissed()` below.
404                         })
405                         .addCallback(new Snackbar.Callback() {
406                             @Override
407                             public void onDismissed(Snackbar snackbar, int event) {
408                                 switch (event) {
409                                     // The user pushed the `Undo` button.
410                                     case Snackbar.Callback.DISMISS_EVENT_ACTION:
411                                         // Store `databaseId` in `argumentsBundle`.
412                                         Bundle argumentsBundle = new Bundle();
413                                         argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete);
414
415                                         // Add `argumentsBundle` to `domainSettingsFragment`.
416                                         DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
417                                         domainSettingsFragment.setArguments(argumentsBundle);
418
419                                         // Display the correct fragments.
420                                         if (twoPanedMode) {  // The device in in two-paned mode.
421                                             // Get a `Cursor` with the current contents of the domains database.
422                                             Cursor undoDeleteDomainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
423
424                                             // Setup `domainsCursorAdapter` with `this` context.  `false` disables `autoRequery`.
425                                             CursorAdapter undoDeleteDomainsCursorAdapter = new CursorAdapter(context, undoDeleteDomainsCursor, false) {
426                                                 @Override
427                                                 public View newView(Context context, Cursor cursor, ViewGroup parent) {
428                                                     // Inflate the individual item layout.  `false` does not attach it to the root.
429                                                     return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
430                                                 }
431
432                                                 @Override
433                                                 public void bindView(View view, Context context, Cursor cursor) {
434                                                     // Set the domain name.
435                                                     String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
436                                                     TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
437                                                     domainNameTextView.setText(domainNameString);
438                                                 }
439                                             };
440
441                                             // Update the `ListView`.
442                                             domainsListView.setAdapter(undoDeleteDomainsCursorAdapter);
443                                             // Select the previously deleted domain in `domainsListView`.
444                                             domainsListView.setItemChecked(deletedDomainPosition, true);
445
446                                             // Display `domainSettingsFragment`.
447                                             supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit();
448
449                                             // Enable the options `MenuItems`.
450                                             deleteMenuItem.setEnabled(true);
451                                             deleteMenuItem.setIcon(R.drawable.delete_light);
452                                         } else {  // The device in in one-paned mode.
453                                             // Display `domainSettingsFragment`.
454                                             supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
455
456                                             // Hide `add_domain_fab`.
457                                             FloatingActionButton addDomainFAB = findViewById(R.id.add_domain_fab);
458                                             addDomainFAB.setVisibility(View.GONE);
459
460                                             // Show and enable `deleteMenuItem`.
461                                             deleteMenuItem.setVisible(true);
462
463                                             // Display `domainSettingsFragment`.
464                                             supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
465                                         }
466                                         break;
467
468                                     // The `Snackbar` was dismissed without the `Undo` button being pushed.
469                                     default:
470                                         // Delete the selected domain.
471                                         domainsDatabaseHelper.deleteDomain(databaseIdToDelete);
472
473                                         // Enable the delete menu item if the system was waiting for a snackbar to be dismissed.
474                                         if (dismissingSnackbar) {
475                                             // Create a `Runnable` to enable the delete menu item.
476                                             Runnable enableDeleteMenuItemRunnable = () -> {
477                                                 // Enable `deleteMenuItem` according to the display mode.
478                                                 if (twoPanedMode) {  // Two-paned mode.
479                                                     // Enable `deleteMenuItem`.
480                                                     deleteMenuItem.setEnabled(true);
481
482                                                     // Set the delete icon according to the theme.
483                                                     if (MainWebViewActivity.darkTheme) {
484                                                         deleteMenuItem.setIcon(R.drawable.delete_dark);
485                                                     } else {
486                                                         deleteMenuItem.setIcon(R.drawable.delete_light);
487                                                     }
488                                                 } else {  // Single-paned mode.
489                                                     // Show `deleteMenuItem`.
490                                                     deleteMenuItem.setVisible(true);
491                                                 }
492
493                                                 // Reset `dismissingSnackbar`.
494                                                 dismissingSnackbar = false;
495                                             };
496
497                                             // Enable the delete menu icon after 100 milliseconds to make sure that the previous domain has been deleted from the database.
498                                             Handler handler = new Handler();
499                                             handler.postDelayed(enableDeleteMenuItemRunnable, 100);
500                                         }
501
502                                         // Close the activity if back was pressed.
503                                         if (closeActivityAfterDismissingSnackbar) {
504                                             // Go home.
505                                             NavUtils.navigateUpFromSameTask(activity);
506                                         }
507
508                                         break;
509                                 }
510                             }
511                         });
512
513                 // Show the Snackbar.
514                 undoDeleteSnackbar.show();
515                 break;
516         }
517
518         // Consume the event.
519         return true;
520     }
521
522     @Override
523     protected void onSaveInstanceState(Bundle outState) {
524         // Store the current `DomainSettingsFragment` state in `outState`.
525         if (findViewById(R.id.domain_settings_scrollview) != null) {  // `DomainSettingsFragment` is displayed.
526             // Save any changes that have been made to the domain settings.
527             saveDomainSettings(coordinatorLayout, resources);
528
529             // Store `DomainSettingsDisplayed`.
530             outState.putBoolean("domainSettingsDisplayed", true);
531             outState.putInt("domainSettingsDatabaseId", DomainSettingsFragment.databaseId);
532         } else {  // `DomainSettingsFragment` is not displayed.
533             outState.putBoolean("domainSettingsDisplayed", false);
534             outState.putInt("domainSettingsDatabaseId", -1);
535         }
536
537         super.onSaveInstanceState(outState);
538     }
539
540     // Control what the navigation bar back button does.
541     @Override
542     public void onBackPressed() {
543         if (twoPanedMode) {  // The device is in two-paned mode.
544             // Save the current domain settings if the domain settings fragment is displayed.
545             if (findViewById(R.id.domain_settings_scrollview) != null) {
546                 saveDomainSettings(coordinatorLayout, resources);
547             }
548
549             // Dismiss the undo delete SnackBar if it is shown.
550             if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) {
551                 // Set the close flag.
552                 closeActivityAfterDismissingSnackbar = true;
553
554                 // Dismiss the snackbar.
555                 undoDeleteSnackbar.dismiss();
556             } else {
557                 // Pass `onBackPressed()` to the system.
558                 super.onBackPressed();
559             }
560         } else if (closeOnBack) {  // Go directly back to the main WebView activity because the domains activity was launched from the options menu.
561             // Save the current domain settings.
562             saveDomainSettings(coordinatorLayout, resources);
563
564             // Pass `onBackPressed()` to the system.
565             super.onBackPressed();
566         } else if (findViewById(R.id.domain_settings_scrollview) != null) {  // The device is in single-paned mode and domain settings fragment is displayed.
567             // Save the current domain settings.
568             saveDomainSettings(coordinatorLayout, resources);
569
570             // Display the domains list fragment.
571             DomainsListFragment domainsListFragment = new DomainsListFragment();
572             supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
573             supportFragmentManager.executePendingTransactions();
574
575             // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
576             populateDomainsListView(-1);
577
578             // Display the add domain FAB.
579             addDomainFAB.setVisibility(View.VISIBLE);
580
581             // Hide the delete menu item.
582             deleteMenuItem.setVisible(false);
583         } else {  // The device is in single-paned mode and the domain list fragment is displayed.
584             // Dismiss the undo delete SnackBar if it is shown.
585             if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) {
586                 // Set the close flag.
587                 closeActivityAfterDismissingSnackbar = true;
588
589                 // Dismiss the snackbar.
590                 undoDeleteSnackbar.dismiss();
591             } else {
592                 // Pass `onBackPressed()` to the system.
593                 super.onBackPressed();
594             }
595         }
596     }
597
598     @Override
599     public void onAddDomain(AppCompatDialogFragment dialogFragment) {
600         // Dismiss the undo delete snackbar if it is currently displayed.
601         if ((undoDeleteSnackbar != null) && undoDeleteSnackbar.isShown()) {
602             undoDeleteSnackbar.dismiss();
603         }
604
605         // Get the new domain name String from the dialog fragment.
606         EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
607         String domainNameString = domainNameEditText.getText().toString();
608
609         // Create the domain and store the database ID in `currentDomainDatabaseId`.
610         currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString);
611
612         // Display the newly created domain.
613         if (twoPanedMode) {  // The device in in two-paned mode.
614             populateDomainsListView(currentDomainDatabaseId);
615         } else {  // The device is in single-paned mode.
616             // Hide `add_domain_fab`.
617             addDomainFAB.setVisibility(View.GONE);
618
619             // Show and enable `deleteMenuItem`.
620             DomainsActivity.deleteMenuItem.setVisible(true);
621
622             // Add `currentDomainDatabaseId` to `argumentsBundle`.
623             Bundle argumentsBundle = new Bundle();
624             argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
625
626             // Add `argumentsBundle` to `domainSettingsFragment`.
627             DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
628             domainSettingsFragment.setArguments(argumentsBundle);
629
630             // Display `domainSettingsFragment`.
631             supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
632         }
633     }
634
635     public void saveDomainSettings(View view, Resources resources) {
636         // Get handles for the domain settings.
637         EditText domainNameEditText = view.findViewById(R.id.domain_settings_name_edittext);
638         Switch javaScriptSwitch = view.findViewById(R.id.domain_settings_javascript_switch);
639         Switch firstPartyCookiesSwitch = view.findViewById(R.id.domain_settings_first_party_cookies_switch);
640         Switch thirdPartyCookiesSwitch = view.findViewById(R.id.domain_settings_third_party_cookies_switch);
641         Switch domStorageSwitch = view.findViewById(R.id.domain_settings_dom_storage_switch);
642         Switch formDataSwitch = view.findViewById(R.id.domain_settings_form_data_switch);  // Form data can be removed once the minimum API >= 26.
643         Switch easyListSwitch = view.findViewById(R.id.domain_settings_easylist_switch);
644         Switch easyPrivacySwitch = view.findViewById(R.id.domain_settings_easyprivacy_switch);
645         Switch fanboysAnnoyanceSwitch = view.findViewById(R.id.domain_settings_fanboys_annoyance_list_switch);
646         Switch fanboysSocialBlockingSwitch = view.findViewById(R.id.domain_settings_fanboys_social_blocking_list_switch);
647         Switch ultraPrivacySwitch = view.findViewById(R.id.domain_settings_ultraprivacy_switch);
648         Switch blockAllThirdPartyRequestsSwitch = view.findViewById(R.id.domain_settings_block_all_third_party_requests_switch);
649         Spinner userAgentSpinner = view.findViewById(R.id.domain_settings_user_agent_spinner);
650         EditText customUserAgentEditText = view.findViewById(R.id.domain_settings_custom_user_agent_edittext);
651         Spinner fontSizeSpinner = view.findViewById(R.id.domain_settings_font_size_spinner);
652         Spinner swipeToRefreshSpinner = view.findViewById(R.id.domain_settings_swipe_to_refresh_spinner);
653         Spinner displayWebpageImagesSpinner = view.findViewById(R.id.domain_settings_display_webpage_images_spinner);
654         Spinner nightModeSpinner = view.findViewById(R.id.domain_settings_night_mode_spinner);
655         Switch pinnedSslCertificateSwitch = view.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
656         RadioButton savedSslCertificateRadioButton = view.findViewById(R.id.saved_ssl_certificate_radiobutton);
657         RadioButton currentWebsiteCertificateRadioButton = view.findViewById(R.id.current_website_certificate_radiobutton);
658
659         // Extract the data for the domain settings.
660         String domainNameString = domainNameEditText.getText().toString();
661         boolean javaScriptEnabled = javaScriptSwitch.isChecked();
662         boolean firstPartyCookiesEnabled = firstPartyCookiesSwitch.isChecked();
663         boolean thirdPartyCookiesEnabled = thirdPartyCookiesSwitch.isChecked();
664         boolean domStorageEnabled  = domStorageSwitch.isChecked();
665         boolean formDataEnabled = formDataSwitch.isChecked();  // Form data can be removed once the minimum API >= 26.
666         boolean easyListEnabled = easyListSwitch.isChecked();
667         boolean easyPrivacyEnabled = easyPrivacySwitch.isChecked();
668         boolean fanboysAnnoyanceEnabled = fanboysAnnoyanceSwitch.isChecked();
669         boolean fanboysSocialBlockingEnabled = fanboysSocialBlockingSwitch.isChecked();
670         boolean ultraPrivacyEnabled = ultraPrivacySwitch.isChecked();
671         boolean blockAllThirdPartyRequests = blockAllThirdPartyRequestsSwitch.isChecked();
672         int userAgentPosition = userAgentSpinner.getSelectedItemPosition();
673         int fontSizePosition = fontSizeSpinner.getSelectedItemPosition();
674         int swipeToRefreshInt = swipeToRefreshSpinner.getSelectedItemPosition();
675         int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
676         int nightModeInt = nightModeSpinner.getSelectedItemPosition();
677         boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
678
679         // Initialize the user agent name string.
680         String userAgentName;
681
682         // Set the user agent name.
683         switch (userAgentPosition) {
684             case MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT:
685                 // Set the user agent name to be `System default user agent`.
686                 userAgentName = resources.getString(R.string.system_default_user_agent);
687                 break;
688
689             case MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT:
690                 // Set the user agent name to be the custom user agent.
691                 userAgentName = customUserAgentEditText.getText().toString();
692                 break;
693
694             default:
695                 // Get the array of user agent names.
696                 String[] userAgentNameArray = resources.getStringArray(R.array.user_agent_names);
697
698                 // 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.
699                 userAgentName = userAgentNameArray[userAgentPosition - 1];
700         }
701
702         // Get the font size integer.
703         int fontSizeInt = Integer.parseInt(resources.getStringArray(R.array.domain_settings_font_size_entry_values)[fontSizePosition]);
704
705         // Save the domain settings.
706         if (savedSslCertificateRadioButton.isChecked()) {  // The current certificate is being used.
707             // Update the database except for the certificate.
708             domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled,
709                     domStorageEnabled, formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, ultraPrivacyEnabled, blockAllThirdPartyRequests,
710                     userAgentName, fontSizeInt, swipeToRefreshInt, nightModeInt, displayWebpageImagesInt, pinnedSslCertificate);
711         } else if (currentWebsiteCertificateRadioButton.isChecked()) {  // The certificate is being updated with the current website certificate.
712             // Get the current website SSL certificate.
713             SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
714
715             // Store the values from the SSL certificate.
716             String issuedToCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName();
717             String issuedToOrganization = currentWebsiteSslCertificate.getIssuedTo().getOName();
718             String issuedToOrganizationalUnit = currentWebsiteSslCertificate.getIssuedTo().getUName();
719             String issuedByCommonName = currentWebsiteSslCertificate.getIssuedBy().getCName();
720             String issuedByOrganization = currentWebsiteSslCertificate.getIssuedBy().getOName();
721             String issuedByOrganizationalUnit = currentWebsiteSslCertificate.getIssuedBy().getUName();
722             long startDateLong = currentWebsiteSslCertificate.getValidNotBeforeDate().getTime();
723             long endDateLong = currentWebsiteSslCertificate.getValidNotAfterDate().getTime();
724
725             // Update the database.
726             domainsDatabaseHelper.updateDomainWithCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled, domStorageEnabled,
727                     formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, ultraPrivacyEnabled, blockAllThirdPartyRequests, userAgentName, fontSizeInt,
728                     swipeToRefreshInt, nightModeInt, displayWebpageImagesInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName,
729                     issuedByOrganization, issuedByOrganizationalUnit, startDateLong, endDateLong);
730
731         } else {  // No certificate is selected.
732             // Update the database, with PINNED_SSL_CERTIFICATE set to false.
733             domainsDatabaseHelper.updateDomainExceptCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled, domStorageEnabled,
734                     formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, ultraPrivacyEnabled, blockAllThirdPartyRequests, userAgentName, fontSizeInt,
735                     swipeToRefreshInt, nightModeInt, displayWebpageImagesInt,false);
736         }
737     }
738
739     private void populateDomainsListView(final int highlightedDomainDatabaseId) {
740         // get a handle for the current `domains_listview`.
741         domainsListView = findViewById(R.id.domains_listview);
742
743         // Get a `Cursor` with the current contents of the domains database.
744         Cursor domainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
745
746         // Setup `domainsCursorAdapter` with `this` context.  `false` disables `autoRequery`.
747         CursorAdapter domainsCursorAdapter = new CursorAdapter(context, domainsCursor, false) {
748             @Override
749             public View newView(Context context, Cursor cursor, ViewGroup parent) {
750                 // Inflate the individual item layout.  `false` does not attach it to the root.
751                 return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
752             }
753
754             @Override
755             public void bindView(View view, Context context, Cursor cursor) {
756                 // Set the domain name.
757                 String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
758                 TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
759                 domainNameTextView.setText(domainNameString);
760             }
761         };
762
763         // Update the `ListView`.
764         domainsListView.setAdapter(domainsCursorAdapter);
765
766         // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain.
767         if (DomainsActivity.twoPanedMode && (domainsCursor.getCount() > 0)) {  // Two-paned mode is enabled and there is at least one domain.
768             // Initialize `highlightedDomainPosition`.
769             int highlightedDomainPosition = 0;
770
771             // Get the cursor position for the highlighted domain.
772             for (int i = 0; i < domainsCursor.getCount(); i++) {
773                 // Move to position `i` in the cursor.
774                 domainsCursor.moveToPosition(i);
775
776                 // Get the database ID for this position.
777                 int currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
778
779                 // Set `highlightedDomainPosition` if the database ID for this matches `highlightedDomainDatabaseId`.
780                 if (highlightedDomainDatabaseId == currentDatabaseId) {
781                     highlightedDomainPosition = i;
782                 }
783             }
784
785             // Select the highlighted domain.
786             domainsListView.setItemChecked(highlightedDomainPosition, true);
787
788             // Get the `databaseId` for the highlighted domain.
789             domainsCursor.moveToPosition(highlightedDomainPosition);
790             currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
791
792             // Store `databaseId` in `argumentsBundle`.
793             Bundle argumentsBundle = new Bundle();
794             argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
795
796             // Add `argumentsBundle` to `domainSettingsFragment`.
797             DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
798             domainSettingsFragment.setArguments(argumentsBundle);
799
800             // Display `domainSettingsFragment`.
801             supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit();
802
803             // Enable the options `MenuItems`.
804             deleteMenuItem.setEnabled(true);
805
806             // Set the delete icon according to the theme.
807             if (MainWebViewActivity.darkTheme) {
808                 deleteMenuItem.setIcon(R.drawable.delete_dark);
809             } else {
810                 deleteMenuItem.setIcon(R.drawable.delete_light);
811             }
812         } else if (twoPanedMode) {  // Two-paned mode is enabled but there are no domains.
813             // Disable the options `MenuItems`.
814             deleteMenuItem.setEnabled(false);
815             deleteMenuItem.setIcon(R.drawable.delete_blue);
816         }
817     }
818
819     @Override
820     public void onDestroy() {
821         // Close the domains database helper.
822         domainsDatabaseHelper.close();
823
824         // Run the default commands.
825         super.onDestroy();
826     }
827 }