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