]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
Add blocklist controls to domain settings. https://redmine.stoutner.com/issues/223
[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.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 = super::onBackPressed;
521
522                 // Navigate home after 300 milliseconds to make sure that the previous domain has been deleted from the database.
523                 Handler handler = new Handler();
524                 handler.postDelayed(navigateHomeRunnable, 300);
525             } else {
526                 // Pass `onBackPressed()` to the system.
527                 super.onBackPressed();
528             }
529         } else if (findViewById(R.id.domain_settings_scrollview) != null) {  // The device is in single-paned mode and `DomainSettingsFragment` is displayed.
530             // Save the current domain settings.
531             saveDomainSettings();
532
533             // Display the domains list fragment.
534             DomainsListFragment domainsListFragment = new DomainsListFragment();
535             supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
536             supportFragmentManager.executePendingTransactions();
537
538             // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
539             populateDomainsListView(-1);
540
541             // Display `addDomainFAB`.
542             addDomainFAB.setVisibility(View.VISIBLE);
543
544             // Hide `deleteMenuItem`.
545             deleteMenuItem.setVisible(false);
546         } else {  // The device is in single-paned mode and `DomainsListFragment` is displayed.
547             // Dismiss the undo delete `SnackBar` if it is shown.
548             if (undoDeleteSnackbar != null && undoDeleteSnackbar.isShown()) {
549                 undoDeleteSnackbar.dismiss();
550
551                 // Create a runnable to return to the main activity.
552                 Runnable navigateHomeRunnable = super::onBackPressed;
553
554                 // Navigate home after 300 milliseconds to make sure that the previous domain has been deleted from the database.
555                 Handler handler = new Handler();
556                 handler.postDelayed(navigateHomeRunnable, 300);
557             } else {
558                 // Pass `onBackPressed()` to the system.
559                 super.onBackPressed();
560             }
561         }
562     }
563
564     @Override
565     public void onAddDomain(AppCompatDialogFragment dialogFragment) {
566         // Dismiss the undo delete snackbar if it is currently displayed.
567         if ((undoDeleteSnackbar != null) && (undoDeleteSnackbar.isShown())) {
568             undoDeleteSnackbar.dismiss();
569         }
570
571         // Get the new domain name String from the dialog fragment.
572         EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
573         String domainNameString = domainNameEditText.getText().toString();
574
575         // Create the domain and store the database ID in `currentDomainDatabaseId`.
576         currentDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString);
577
578         // Display the newly created domain.
579         if (twoPanedMode) {  // The device in in two-paned mode.
580             populateDomainsListView(currentDomainDatabaseId);
581         } else {  // The device is in single-paned mode.
582             // Hide `add_domain_fab`.
583             addDomainFAB.setVisibility(View.GONE);
584
585             // Show and enable `deleteMenuItem`.
586             DomainsActivity.deleteMenuItem.setVisible(true);
587
588             // Add `currentDomainDatabaseId` to `argumentsBundle`.
589             Bundle argumentsBundle = new Bundle();
590             argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
591
592             // Add `argumentsBundle` to `domainSettingsFragment`.
593             DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
594             domainSettingsFragment.setArguments(argumentsBundle);
595
596             // Display `domainSettingsFragment`.
597             supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
598         }
599     }
600
601     private void saveDomainSettings() {
602         // Get handles for the domain settings.
603         EditText domainNameEditText = findViewById(R.id.domain_settings_name_edittext);
604         Switch javaScriptSwitch = findViewById(R.id.domain_settings_javascript_switch);
605         Switch firstPartyCookiesSwitch = findViewById(R.id.domain_settings_first_party_cookies_switch);
606         Switch thirdPartyCookiesSwitch = findViewById(R.id.domain_settings_third_party_cookies_switch);
607         Switch domStorageSwitch = findViewById(R.id.domain_settings_dom_storage_switch);
608         Switch formDataSwitch = findViewById(R.id.domain_settings_form_data_switch);
609         Switch easyListSwitch = findViewById(R.id.domain_settings_easylist_switch);
610         Switch easyPrivacySwitch = findViewById(R.id.domain_settings_easyprivacy_switch);
611         Switch fanboysAnnoyanceSwitch = findViewById(R.id.domain_settings_fanboys_annoyance_list_switch);
612         Switch fanboysSocialBlockingSwitch = findViewById(R.id.domain_settings_fanboys_social_blocking_list_switch);
613         Spinner userAgentSpinner = findViewById(R.id.domain_settings_user_agent_spinner);
614         EditText customUserAgentEditText = findViewById(R.id.domain_settings_custom_user_agent_edittext);
615         Spinner fontSizeSpinner = findViewById(R.id.domain_settings_font_size_spinner);
616         Spinner displayWebpageImagesSpinner = findViewById(R.id.domain_settings_display_webpage_images_spinner);
617         Spinner nightModeSpinner = findViewById(R.id.domain_settings_night_mode_spinner);
618         Switch pinnedSslCertificateSwitch = findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
619         RadioButton savedSslCertificateRadioButton = findViewById(R.id.saved_ssl_certificate_radiobutton);
620         RadioButton currentWebsiteCertificateRadioButton = findViewById(R.id.current_website_certificate_radiobutton);
621
622         // Extract the data for the domain settings.
623         String domainNameString = domainNameEditText.getText().toString();
624         boolean javaScriptEnabled = javaScriptSwitch.isChecked();
625         boolean firstPartyCookiesEnabled = firstPartyCookiesSwitch.isChecked();
626         boolean thirdPartyCookiesEnabled = thirdPartyCookiesSwitch.isChecked();
627         boolean domStorageEnabled  = domStorageSwitch.isChecked();
628         boolean formDataEnabled = formDataSwitch.isChecked();
629         boolean easyListEnabled = easyListSwitch.isChecked();
630         boolean easyPrivacyEnabled = easyPrivacySwitch.isChecked();
631         boolean fanboysAnnoyanceEnabled = fanboysAnnoyanceSwitch.isChecked();
632         boolean fanboysSocialBlockingEnabled = fanboysSocialBlockingSwitch.isChecked();
633         int userAgentPositionInt = userAgentSpinner.getSelectedItemPosition();
634         int fontSizePositionInt = fontSizeSpinner.getSelectedItemPosition();
635         int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
636         int nightModeInt = nightModeSpinner.getSelectedItemPosition();
637         boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
638
639         // Get the data for the `Spinners` from the entry values string arrays.
640         String userAgentString = getResources().getStringArray(R.array.domain_settings_user_agent_entry_values)[userAgentPositionInt];
641         int fontSizeInt = Integer.parseInt(getResources().getStringArray(R.array.domain_settings_font_size_entry_values)[fontSizePositionInt]);
642
643         // Check to see if we are using a custom user agent.
644         if (userAgentString.equals("Custom user agent")) {
645             // Set `userAgentString` to the custom user agent string.
646             userAgentString = customUserAgentEditText.getText().toString();
647         }
648
649         // Save the domain settings.
650         if (savedSslCertificateRadioButton.isChecked()) {  // The current certificate is being used.
651             // Update the database except for the certificate.
652             domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled,
653                     domStorageEnabled, formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, userAgentString, fontSizeInt, displayWebpageImagesInt,
654                     nightModeInt, pinnedSslCertificate);
655         } else if (currentWebsiteCertificateRadioButton.isChecked()) {  // The certificate is being updated with the current website certificate.
656             // Get the current website SSL certificate.
657             SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
658
659             // Store the values from the SSL certificate.
660             String issuedToCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName();
661             String issuedToOrganization = currentWebsiteSslCertificate.getIssuedTo().getOName();
662             String issuedToOrganizationalUnit = currentWebsiteSslCertificate.getIssuedTo().getUName();
663             String issuedByCommonName = currentWebsiteSslCertificate.getIssuedBy().getCName();
664             String issuedByOrganization = currentWebsiteSslCertificate.getIssuedBy().getOName();
665             String issuedByOrganizationalUnit = currentWebsiteSslCertificate.getIssuedBy().getUName();
666             long startDateLong = currentWebsiteSslCertificate.getValidNotBeforeDate().getTime();
667             long endDateLong = currentWebsiteSslCertificate.getValidNotAfterDate().getTime();
668
669             // Update the database.
670             domainsDatabaseHelper.updateDomainWithCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled, domStorageEnabled,
671                     formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, userAgentString, fontSizeInt, displayWebpageImagesInt, nightModeInt,
672                     pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization, issuedByOrganizationalUnit, startDateLong, endDateLong);
673
674         } else {  // No certificate is selected.
675             // Update the database, with PINNED_SSL_CERTIFICATE set to false.
676             domainsDatabaseHelper.updateDomainExceptCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled, domStorageEnabled,
677                     formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, userAgentString, fontSizeInt, displayWebpageImagesInt, nightModeInt,
678                     false);
679         }
680     }
681
682     private void populateDomainsListView(final int highlightedDomainDatabaseId) {
683         // get a handle for the current `domains_listview`.
684         domainsListView = findViewById(R.id.domains_listview);
685
686         // Get a `Cursor` with the current contents of the domains database.
687         Cursor domainsCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
688
689         // Setup `domainsCursorAdapter` with `this` context.  `false` disables `autoRequery`.
690         CursorAdapter domainsCursorAdapter = new CursorAdapter(context, domainsCursor, false) {
691             @Override
692             public View newView(Context context, Cursor cursor, ViewGroup parent) {
693                 // Inflate the individual item layout.  `false` does not attach it to the root.
694                 return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false);
695             }
696
697             @Override
698             public void bindView(View view, Context context, Cursor cursor) {
699                 // Set the domain name.
700                 String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
701                 TextView domainNameTextView = view.findViewById(R.id.domain_name_textview);
702                 domainNameTextView.setText(domainNameString);
703             }
704         };
705
706         // Update the `ListView`.
707         domainsListView.setAdapter(domainsCursorAdapter);
708
709         // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain.
710         if (DomainsActivity.twoPanedMode && (domainsCursor.getCount() > 0)) {  // Two-paned mode is enabled and there is at least one domain.
711             // Initialize `highlightedDomainPosition`.
712             int highlightedDomainPosition = 0;
713
714             // Get the cursor position for the highlighted domain.
715             for (int i = 0; i < domainsCursor.getCount(); i++) {
716                 // Move to position `i` in the cursor.
717                 domainsCursor.moveToPosition(i);
718
719                 // Get the database ID for this position.
720                 int currentDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
721
722                 // Set `highlightedDomainPosition` if the database ID for this matches `highlightedDomainDatabaseId`.
723                 if (highlightedDomainDatabaseId == currentDatabaseId) {
724                     highlightedDomainPosition = i;
725                 }
726             }
727
728             // Select the highlighted domain.
729             domainsListView.setItemChecked(highlightedDomainPosition, true);
730
731             // Get the `databaseId` for the highlighted domain.
732             domainsCursor.moveToPosition(highlightedDomainPosition);
733             currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
734
735             // Store `databaseId` in `argumentsBundle`.
736             Bundle argumentsBundle = new Bundle();
737             argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
738
739             // Add `argumentsBundle` to `domainSettingsFragment`.
740             DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
741             domainSettingsFragment.setArguments(argumentsBundle);
742
743             // Display `domainSettingsFragment`.
744             supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit();
745
746             // Enable the options `MenuItems`.
747             deleteMenuItem.setEnabled(true);
748
749             // Set the delete icon according to the theme.
750             if (MainWebViewActivity.darkTheme) {
751                 deleteMenuItem.setIcon(R.drawable.delete_dark);
752             } else {
753                 deleteMenuItem.setIcon(R.drawable.delete_light);
754             }
755         } else if (twoPanedMode) {  // Two-paned mode is enabled but there are no domains.
756             // Disable the options `MenuItems`.
757             deleteMenuItem.setEnabled(false);
758             deleteMenuItem.setIcon(R.drawable.delete_blue);
759         }
760     }
761 }