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