]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Save and restore the app state. https://redmine.stoutner.com/issues/461
authorSoren Stoutner <soren@stoutner.com>
Fri, 14 Aug 2020 21:54:30 +0000 (14:54 -0700)
committerSoren Stoutner <soren@stoutner.com>
Fri, 14 Aug 2020 21:54:30 +0000 (14:54 -0700)
48 files changed:
app/build.gradle
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java
app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.java
app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java
app/src/main/java/com/stoutner/privacybrowser/adapters/WebViewPagerAdapter.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetLogcat.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetSourceBackgroundTask.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java
app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java
app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java
app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTabFragment.java
app/src/main/java/com/stoutner/privacybrowser/fragments/WebViewTabFragment.java
app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java
app/src/main/java/com/stoutner/privacybrowser/viewmodelfactories/WebViewSourceFactory.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/viewmodels/WebViewSource.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java
app/src/main/res/color/primary_text_color_selector_dark.xml [deleted file]
app/src/main/res/color/primary_text_color_selector_light.xml [deleted file]
app/src/main/res/color/spinner_color_selector_dark.xml [deleted file]
app/src/main/res/color/spinner_color_selector_light.xml [deleted file]
app/src/main/res/layout-w900dp/domains_list_fragment.xml
app/src/main/res/layout/domain_settings_fragment.xml
app/src/main/res/layout/domains_list_fragment.xml
app/src/main/res/layout/logcat_coordinatorlayout.xml
app/src/main/res/layout/spinner_dropdown_items.xml
app/src/main/res/layout/view_source_coordinatorlayout.xml
app/src/main/res/layout/webview_framelayout.xml
app/src/main/res/menu/bookmarks_databaseview_options_menu.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-fr/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values-v23/styles.xml
app/src/main/res/values-v27/styles.xml
app/src/main/res/values/attrs.xml
app/src/main/res/values/styles.xml
build.gradle

index 91d353a7b6230c0904b5fb358bd39cba528ec7a4..fe01dec606e9037ab2861b07860cd28202ab63fc 100644 (file)
@@ -79,13 +79,13 @@ dependencies {
     // Include the following AndroidX libraries.
     implementation 'androidx.arch.core:core-common:2.1.0'
     implementation 'androidx.arch.core:core-runtime:2.1.0'
-    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'androidx.appcompat:appcompat:1.2.0'
     implementation 'androidx.cardview:cardview:1.0.0'
     implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
-    implementation "androidx.core:core-ktx:1.3.0"
-    implementation 'androidx.drawerlayout:drawerlayout:1.0.0'
+    implementation "androidx.core:core-ktx:1.3.1"
+    implementation 'androidx.drawerlayout:drawerlayout:1.1.0'
     implementation 'androidx.preference:preference:1.1.1'
-    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
+    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
     implementation 'androidx.viewpager:viewpager:1.0.0'
     implementation 'androidx.webkit:webkit:1.2.0'
 
@@ -93,8 +93,8 @@ dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72"
 
     // Include the Google material library.
-    implementation 'com.google.android.material:material:1.1.0'
+    implementation 'com.google.android.material:material:1.2.0'
 
     // Only compile Firebase ads for the free flavor.
-    freeImplementation 'com.google.firebase:firebase-ads:19.1.0'
+    freeImplementation 'com.google.firebase:firebase-ads:19.3.0'
 }
\ No newline at end of file
index 3a96929f897263bf1590279083faf4314a5b409a..735d3e39793c951699bb31475e881deb6d2fab0d 100644 (file)
@@ -52,7 +52,7 @@ import android.widget.TextView;
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;  // The AndroidX toolbar must be used until the minimum API is >= 21.
+import androidx.appcompat.widget.Toolbar;
 import androidx.core.app.NavUtils;
 import androidx.fragment.app.DialogFragment;
 
@@ -68,6 +68,7 @@ import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
 
 import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
 
 public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
         EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
@@ -85,6 +86,13 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
     public static boolean restartFromBookmarksDatabaseViewActivity;
 
 
+    // Define the saved instance state constants.
+    private final String CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list";
+
+    // Define the class menu items.
+    private MenuItem moveBookmarkUpMenuItem;
+    private MenuItem moveBookmarkDownMenuItem;
+
     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`,
     // `onMoveToFolder()`, `deleteBookmarkFolderContents()`, `loadFolder()`, and `onDestroy()`.
     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
@@ -109,12 +117,6 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
     // `oldFolderName` is used in `onCreate()` and `onSaveBookmarkFolder()`.
     private String oldFolderNameString;
 
-    // `moveBookmarkUpMenuItem` is used in `onCreate()` and `updateMoveIcons()`.
-    private MenuItem moveBookmarkUpMenuItem;
-
-    // `moveBookmarkDownMenuItem` is used in `onCreate()` and `updateMoveIcons()`.
-    private MenuItem moveBookmarkDownMenuItem;
-
     // `bookmarksDeletedSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
     private Snackbar bookmarksDeletedSnackbar;
 
@@ -583,6 +585,22 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
             // Display the create bookmark dialog.
             createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
         });
+
+        // Restore the state if the app has been restarted.
+        if (savedInstanceState != null) {
+            // Update the bookmarks list view after it has loaded.
+            bookmarksListView.post(() -> {
+                // Get the checked bookmarks array list.
+                ArrayList<Integer> checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST);
+
+                // Check each previously checked bookmark in the list view.  When the minimum API >= 24 a `forEach()` command can be used instead.
+                if (checkedBookmarksArrayList != null) {
+                    for (int i = 0; i < checkedBookmarksArrayList.size(); i++) {
+                        bookmarksListView.setItemChecked(checkedBookmarksArrayList.get(i), true);
+                    }
+                }
+            });
+        }
     }
 
     @Override
@@ -600,6 +618,30 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         }
     }
 
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Get the array of the checked items.
+        SparseBooleanArray checkedBookmarksSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
+
+        // Create a checked items array list.
+        ArrayList<Integer> checkedBookmarksArrayList = new ArrayList<>();
+
+        // Add each checked bookmark position to the array list.
+        for (int i = 0; i < checkedBookmarksSparseBooleanArray.size(); i++) {
+            // Check to see if the bookmark is currently checked.  Bookmarks that have previously been checked but currently aren't will be populated in the sparse boolean array, but will return false.
+            if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
+                // Add the bookmark position to the checked bookmarks array list.
+                checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i));
+            }
+        }
+
+        // Store the checked items array list in the saved instance state.
+        savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList);
+    }
+
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         // Inflate the menu.
index c4b97517f12b4624ebd5b4985586c8c710de333d..17c9e879fdec06cad0266b5c9498274729d11dac 100644 (file)
@@ -72,35 +72,24 @@ import java.util.Arrays;
 
 public class BookmarksDatabaseViewActivity extends AppCompatActivity implements EditBookmarkDatabaseViewDialog.EditBookmarkDatabaseViewListener,
         EditBookmarkFolderDatabaseViewDialog.EditBookmarkFolderDatabaseViewListener {
-    // Instantiate the constants.
+    // Define the class constants.
     private static final int ALL_FOLDERS_DATABASE_ID = -2;
     public static final int HOME_FOLDER_DATABASE_ID = -1;
 
-    // `bookmarksDatabaseHelper` is used in `onCreate()`, `updateBookmarksListView()`, `selectAllBookmarksInFolder()`, and `onDestroy()`.
-    private BookmarksDatabaseHelper bookmarksDatabaseHelper;
-
-    // `bookmarksCursor` is used in `onCreate()`, `updateBookmarksListView()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, and `onDestroy()`.
-    private Cursor bookmarksCursor;
-
-    // `bookmarksCursorAdapter` is used in `onCreate()`, `selectAllBookmarksInFolder()`, and `updateBookmarksListView()`.
-    private CursorAdapter bookmarksCursorAdapter;
+    // Define the saved instance state constants.
+    private final String CURRENT_FOLDER_DATABASE_ID = "current_folder_database_id";
+    private final String CURRENT_FOLDER_NAME = "current_folder_name";
+    private final String SORT_BY_DISPLAY_ORDER = "sort_by_display_order";
 
-    // `oldFolderNameString` is used in `onCreate()` and `onSaveBookmarkFolder()`.
-    private String oldFolderNameString;
-
-    // `currentFolderDatabaseId` is used in `onCreate()`, `updateBookmarksListView()`, `onSaveBookmark()`, and `onSaveBookmarkFolder()`.
+    // Define the class variables.
     private int currentFolderDatabaseId;
-
-    // `currentFolder` is used in `onCreate()`, `onSaveBookmark()`, and `onSaveBookmarkFolder()`.
     private String currentFolderName;
-
-    // `sortByDisplayOrder` is used in `onCreate()`, `onOptionsItemSelected()`, and `updateBookmarksListView()`.
     private boolean sortByDisplayOrder;
-
-    // `bookmarksDeletedSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
+    private BookmarksDatabaseHelper bookmarksDatabaseHelper;
+    private Cursor bookmarksCursor;
+    private CursorAdapter bookmarksCursorAdapter;
+    private String oldFolderNameString;
     private Snackbar bookmarksDeletedSnackbar;
-
-    // `closeActivityAfterDismissingSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
     private boolean closeActivityAfterDismissingSnackbar;
 
     @Override
@@ -137,8 +126,10 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
         // Set the content view.
         setContentView(R.layout.bookmarks_databaseview_coordinatorlayout);
 
-        // The AndroidX toolbar must be used until the minimum API is >= 21.
+        // Get a handle for the toolbar.
         Toolbar toolbar = findViewById(R.id.bookmarks_databaseview_toolbar);
+
+        // Set the support action bar.
         setSupportActionBar(toolbar);
 
         // Get a handle for the action bar.
@@ -163,20 +154,20 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
         // Get a cursor with the list of all the folders.
         Cursor foldersCursor = bookmarksDatabaseHelper.getAllFolders();
 
-        // Combine `matrixCursor` and `foldersCursor`.
+        // Combine the matrix cursor and the folders cursor.
         MergeCursor foldersMergeCursor = new MergeCursor(new Cursor[]{matrixCursor, foldersCursor});
 
 
         // Get the default folder bitmap.  `ContextCompat` must be used until the minimum API >= 21.
         Drawable defaultFolderDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.folder_blue_bitmap);
 
-        // Cast the default folder drawable to a `BitmapDrawable`.
+        // Cast the default folder drawable to a bitmap drawable.
         BitmapDrawable defaultFolderBitmapDrawable = (BitmapDrawable) defaultFolderDrawable;
 
         // Remove the incorrect lint warning that `.getBitmap()` might be null.
         assert defaultFolderBitmapDrawable != null;
 
-        // Convert the default folder `BitmapDrawable` to a bitmap.
+        // Convert the default folder bitmap drawable to a bitmap.
         Bitmap defaultFolderBitmap = defaultFolderBitmapDrawable.getBitmap();
 
 
@@ -232,34 +223,54 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
         Spinner folderSpinner = findViewById(R.id.spinner);
         folderSpinner.setAdapter(foldersCursorAdapter);
 
-        // Handle taps on the spinner dropdown.
-        folderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-                // Store the current folder database ID.
-                currentFolderDatabaseId = (int) id;
+        // Wait to set the on item selected listener until the spinner has been inflated.  Otherwise the activity will crash on restart.
+        folderSpinner.post(() -> {
+            // Handle taps on the spinner dropdown.
+            folderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+                @Override
+                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                    // Store the current folder database ID.
+                    currentFolderDatabaseId = (int) id;
 
-                // Get a handle for the selected view.
-                TextView selectedFolderTextView = findViewById(R.id.spinner_item_textview);
+                    // Get a handle for the selected view.
+                    TextView selectedFolderTextView = findViewById(R.id.spinner_item_textview);
 
-                // Store the current folder name.
-                currentFolderName = selectedFolderTextView.getText().toString();
+                    // Store the current folder name.
+                    currentFolderName = selectedFolderTextView.getText().toString();
 
-                // Update the list view.
-                updateBookmarksListView();
-            }
+                    // Update the list view.
+                    updateBookmarksListView();
+                }
 
-            @Override
-            public void onNothingSelected(AdapterView<?> parent) {
-                // Do nothing.
-            }
+                @Override
+                public void onNothingSelected(AdapterView<?> parent) {
+                    // Do nothing.
+                }
+            });
         });
 
-        // Get a handle for the bookmarks `ListView`.
+
+        // Get a handle for the bookmarks listview.
         ListView bookmarksListView = findViewById(R.id.bookmarks_databaseview_listview);
 
-        // Get a `Cursor` with the current contents of the bookmarks database.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarks();
+        // Check to see if the activity was restarted.
+        if (savedInstanceState == null) {  // The activity was not restarted.
+            // Set the default current folder database ID.
+            currentFolderDatabaseId = ALL_FOLDERS_DATABASE_ID;
+        } else {  // The activity was restarted.
+            // Restore the class variables from the saved instance state.
+            currentFolderDatabaseId = savedInstanceState.getInt(CURRENT_FOLDER_DATABASE_ID);
+            currentFolderName = savedInstanceState.getString(CURRENT_FOLDER_NAME);
+            sortByDisplayOrder = savedInstanceState.getBoolean(SORT_BY_DISPLAY_ORDER);
+
+            // Update the spinner if the home folder is selected.  Android handles this by default for the main cursor but not the matrix cursor.
+            if (currentFolderDatabaseId == HOME_FOLDER_DATABASE_ID) {
+                folderSpinner.setSelection(1);
+            }
+        }
+
+        // Update the bookmarks listview.
+        updateBookmarksListView();
 
         // Setup a `CursorAdapter` with `this` context.  `false` disables autoRequery.
         bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
@@ -347,9 +358,6 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
         // Update the ListView.
         bookmarksListView.setAdapter(bookmarksCursorAdapter);
 
-        // Set the current folder database ID.
-        currentFolderDatabaseId = ALL_FOLDERS_DATABASE_ID;
-
         // Set a listener to edit a bookmark when it is tapped.
         bookmarksListView.setOnItemClickListener((AdapterView<?> parent, View view, int position, long id) -> {
             // Convert the database ID to an int.
@@ -392,6 +400,17 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
                 // Disable the delete menu item if a delete is pending.
                 deleteMenuItem.setEnabled(!deletingBookmarks);
 
+                // Get the number of currently selected bookmarks.
+                int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount();
+
+                // Set the action mode subtitle according to the number of selected bookmarks.  This must be set here or it will be missing if the activity is restarted.
+                mode.setSubtitle(getString(R.string.selected) + "  " + numberOfSelectedBookmarks);
+
+                // Do not show the select all menu item if all the bookmarks are already checked.
+                if (bookmarksListView.getCheckedItemCount() == bookmarksListView.getCount()) {
+                    selectAllMenuItem.setVisible(false);
+                }
+
                 // Make it so.
                 return true;
             }
@@ -407,13 +426,15 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
                 // Calculate the number of selected bookmarks.
                 int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount();
 
-                // Adjust the ActionMode according to the number of selected bookmarks.
+                // Update the action mode subtitle according to the number of selected bookmarks.
                 mode.setSubtitle(getString(R.string.selected) + "  " + numberOfSelectedBookmarks);
 
-                // Do not show the select all menu item if all the bookmarks are already checked.
-                if (bookmarksListView.getCheckedItemCount() == bookmarksListView.getCount()) {
+                // Update the visibility of the the select all menu.
+                if (bookmarksListView.getCheckedItemCount() == bookmarksListView.getCount()) {  // All of the bookmarks are checked.
+                    // Hide the select all menu item.
                     selectAllMenuItem.setVisible(false);
-                } else {
+                } else {  // Not all of the bookmarks are checked.
+                    // Show the select all menu item.
                     selectAllMenuItem.setVisible(true);
                 }
 
@@ -584,6 +605,21 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
         // Inflate the menu.
         getMenuInflater().inflate(R.menu.bookmarks_databaseview_options_menu, menu);
 
+        // Get the current theme status.
+        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+        // Get a handle for the sort menu item.
+        MenuItem sortMenuItem = menu.findItem(R.id.sort);
+
+        // Change the sort menu item icon if the listview is sorted by display order, which restores the state after a restart.
+        if (sortByDisplayOrder) {
+            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                sortMenuItem.setIcon(R.drawable.sort_selected_day);
+            } else {
+                sortMenuItem.setIcon(R.drawable.sort_selected_night);
+            }
+        }
+
         // Success.
         return true;
     }
@@ -600,7 +636,7 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
                 onBackPressed();
                 break;
 
-            case R.id.options_menu_sort:
+            case R.id.sort:
                 // Update the sort by display order tracker.
                 sortByDisplayOrder = !sortByDisplayOrder;
 
@@ -613,20 +649,20 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
                 // Update the icon and display a snackbar.
                 if (sortByDisplayOrder) {  // Sort by display order.
                     // Update the icon according to the theme.
-                    if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        menuItem.setIcon(R.drawable.sort_selected_night);
-                    } else {
+                    if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                         menuItem.setIcon(R.drawable.sort_selected_day);
+                    } else {
+                        menuItem.setIcon(R.drawable.sort_selected_night);
                     }
 
                     // Display a Snackbar indicating the current sort type.
                     Snackbar.make(bookmarksListView, R.string.sorted_by_display_order, Snackbar.LENGTH_SHORT).show();
                 } else {  // Sort by database id.
                     // Update the icon according to the theme.
-                    if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        menuItem.setIcon(R.drawable.sort_night);
-                    } else {
+                    if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                         menuItem.setIcon(R.drawable.sort_day);
+                    } else {
+                        menuItem.setIcon(R.drawable.sort_night);
                     }
 
                     // Display a Snackbar indicating the current sort type.
@@ -640,6 +676,17 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
         return true;
     }
 
+    @Override
+    public void onSaveInstanceState (@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Store the class variables in the bundle.
+        savedInstanceState.putInt(CURRENT_FOLDER_DATABASE_ID, currentFolderDatabaseId);
+        savedInstanceState.putString(CURRENT_FOLDER_NAME, currentFolderName);
+        savedInstanceState.putBoolean(SORT_BY_DISPLAY_ORDER, sortByDisplayOrder);
+    }
+
     @Override
     public void onBackPressed() {
         // Check to see if a snackbar is currently displayed.  If so, it must be closed before existing so that a pending delete is completed before reloading the list view in the bookmarks activity.
@@ -698,8 +745,10 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements
                 }
         }
 
-        // Update the list view.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+        // Update the cursor adapter if it isn't null, which happens when the activity is restarted.
+        if (bookmarksCursorAdapter != null) {
+            bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+        }
     }
 
     private void selectAllBookmarksInFolder(int folderId) {
index 6baf5f6e534df8c4cf7ac969753abeb500c25b0e..5d0589fcf8a8455e19b84998018ab1c8db01e317 100644 (file)
@@ -38,17 +38,18 @@ import android.widget.CursorAdapter;
 import android.widget.EditText;
 import android.widget.ListView;
 import android.widget.RadioButton;
+import android.widget.ScrollView;
 import android.widget.Spinner;
-import android.widget.Switch;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;  // The AndroidX toolbar must be used until the minimum API is >= 21.
+import androidx.appcompat.widget.SwitchCompat;
+import androidx.appcompat.widget.Toolbar;
 import androidx.core.app.NavUtils;
 import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentManager;  // The AndroidX dialog fragment must be used or an error is produced on API <=22.
+import androidx.fragment.app.FragmentManager;
 
 import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.google.android.material.snackbar.Snackbar;
@@ -62,16 +63,11 @@ import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
 import java.util.Objects;
 
 public class DomainsActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener, DomainsListFragment.DismissSnackbarInterface {
-    // `twoPanedMode` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `populateDomainsListView()`.
+    // Define the public static variables.
+    public static int domainsListViewPosition;
     public static boolean twoPanedMode;
-
-    // `databaseId` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onCreateOptionsMenu()`, `saveDomainSettings()` and `populateDomainsListView()`.
     public static int currentDomainDatabaseId;
-
-    // `deleteMenuItem` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`.
     public static MenuItem deleteMenuItem;
-
-    // `dismissingSnackbar` is public static so it can be accessed from `DomainsListFragment`.  It is also used in `onOptionsItemSelected()`.
     public static boolean dismissingSnackbar;
 
     // The SSL certificate and IP address information are accessed from `DomainSettingsFragment` and `saveDomainSettings()`.
@@ -86,6 +82,21 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
     public static String currentIpAddresses;
 
 
+    // Initialize the class constants.
+    private final String LISTVIEW_POSITION = "listview_position";
+    private final String DOMAIN_SETTINGS_DISPLAYED = "domain_settings_displayed";
+    private final String DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_is";
+    private final String DOMAIN_SETTINGS_SCROLL_Y = "domain_settings_scroll_y";
+
+    // Initialize the class variables.
+    private boolean restartAfterRotate;
+    private boolean domainSettingsDisplayedBeforeRotate;
+    private int domainSettingsDatabaseIdBeforeRotate;
+    private int domainSettingsScrollY = 0;
+
+    // Defile the class views.
+    private ListView domainsListView;
+
     // `closeActivityAfterDismissingSnackbar` is used in `onOptionsItemSelected()`, and `onBackPressed()`.
     private boolean closeActivityAfterDismissingSnackbar;
 
@@ -95,24 +106,12 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
     // `domainsDatabaseHelper` is used in `onCreate()`, `saveDomainSettings()`, and `onDestroy()`.
     private static DomainsDatabaseHelper domainsDatabaseHelper;
 
-    // `domainsListView` is used in `onCreate()` and `populateDomainsList()`.
-    private ListView domainsListView;
-
     // `addDomainFAB` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `onBackPressed()`.
     private FloatingActionButton addDomainFAB;
 
     // `deletedDomainPosition` is used in an inner and outer class in `onOptionsItemSelected()`.
     private int deletedDomainPosition;
 
-    // `restartAfterRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
-    private boolean restartAfterRotate;
-
-    // `domainSettingsDisplayedBeforeRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
-    private boolean domainSettingsDisplayedBeforeRotate;
-
-    // `domainSettingsDatabaseIdBeforeRotate` is used in `onCreate()` and `onCreateOptionsMenu()`.
-    private int domainSettingsDatabaseIdBeforeRotate;
-
     // `goDirectlyToDatabaseId` is used in `onCreate()` and `onCreateOptionsMenu()`.
     private int goDirectlyToDatabaseId;
 
@@ -144,11 +143,16 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         // Run the default commands.
         super.onCreate(savedInstanceState);
 
-        // Extract the values from `savedInstanceState` if it is not `null`.
+        // Initialize the domains listview position.
+        domainsListViewPosition = 0;
+
+        // Extract the values from the saved instance state if it is not null.
         if (savedInstanceState != null) {
+            domainsListViewPosition = savedInstanceState.getInt(LISTVIEW_POSITION);
             restartAfterRotate = true;
-            domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean("domain_settings_displayed");
-            domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt("domain_settings_database_id");
+            domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean(DOMAIN_SETTINGS_DISPLAYED);
+            domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt(DOMAIN_SETTINGS_DATABASE_ID);
+            domainSettingsScrollY = savedInstanceState.getInt(DOMAIN_SETTINGS_SCROLL_Y);
         }
 
         // Get the launching intent
@@ -242,7 +246,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                 fragmentManager.executePendingTransactions();
 
                 // Populate the list of domains.  `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation.
-                populateDomainsListView(domainSettingsDatabaseIdBeforeRotate);
+                populateDomainsListView(domainSettingsDatabaseIdBeforeRotate, domainsListViewPosition);
             } else {  // The device is in single-paned mode.
                 // Reset `restartAfterRotate`.
                 restartAfterRotate = false;
@@ -250,21 +254,26 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                 // Store the current domain database ID.
                 currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRotate;
 
-                // Add `currentDomainDatabaseId` to `argumentsBundle`.
+                // Create an arguments bundle.
                 Bundle argumentsBundle = new Bundle();
+
+                // Add the domain settings arguments.
                 argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
+                argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY);
 
-                // Add `argumentsBundle` to `domainSettingsFragment`.
+                // Instantiate a new domain settings fragment.
                 DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
+
+                // Add the arguments bundle to the domain settings fragment.
                 domainSettingsFragment.setArguments(argumentsBundle);
 
-                // Show `deleteMenuItem`.
+                // Show the delete menu item.
                 deleteMenuItem.setVisible(true);
 
                 // Hide the add domain floating action button.
                 addDomainFAB.hide();
 
-                // Display `domainSettingsFragment`.
+                // Display the domain settings fragment.
                 fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
             }
         } else {  // The device was not rotated or, if it was, domain settings were not displayed previously.
@@ -279,23 +288,28 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                     fragmentManager.executePendingTransactions();
 
                     // Populate the list of domains.  `domainSettingsDatabaseId` highlights the domain that was highlighted before the rotation.
-                    populateDomainsListView(goDirectlyToDatabaseId);
+                    populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition);
                 } else {  // The device is in single-paned mode.
-                    // Add the domain ID to be loaded to `argumentsBundle`.
+                    // Create an arguments bundle.
                     Bundle argumentsBundle = new Bundle();
+
+                    // Add the domain settings to arguments bundle.
                     argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, goDirectlyToDatabaseId);
+                    argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY);
 
-                    // Add `argumentsBundle` to `domainSettingsFragment`.
+                    // Instantiate a new domain settings fragment.
                     DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
+
+                    // Add the arguments bundle to the domain settings fragment`.
                     domainSettingsFragment.setArguments(argumentsBundle);
 
-                    // Show `deleteMenuItem`.
+                    // Show the delete menu item.
                     deleteMenuItem.setVisible(true);
 
                     // Hide the add domain floating action button.
                     addDomainFAB.hide();
 
-                    // Display `domainSettingsFragment`.
+                    // Display the domain settings fragment.
                     fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
                 }
             } else {  // Highlight the first domain.
@@ -305,7 +319,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                 fragmentManager.executePendingTransactions();
 
                 // Populate the list of domains.  `-1` highlights the first domain.
-                populateDomainsListView(-1);
+                populateDomainsListView(-1, domainsListViewPosition);
             }
         }
 
@@ -356,7 +370,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                     fragmentManager.executePendingTransactions();
 
                     // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
-                    populateDomainsListView(-1);
+                    populateDomainsListView(-1, domainsListViewPosition);
 
                     // Show the add domain floating action button.
                     addDomainFAB.show();
@@ -448,12 +462,17 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                             public void onDismissed(Snackbar snackbar, int event) {
                                 // Run commands based on the event.
                                 if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The user pushed the `Undo` button.
-                                    // Store the database ID in arguments bundle.
+                                    // Create an arguments bundle.
                                     Bundle argumentsBundle = new Bundle();
+
+                                    // Store the domains settings in the arguments bundle.
                                     argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete);
+                                    argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY);
 
-                                    // Add the arguments bundle to the domain settings fragment.
+                                    // Instantiate a new domain settings fragment.
                                     DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
+
+                                    // Add the arguments bundle to the domain settings fragment.
                                     domainSettingsFragment.setArguments(argumentsBundle);
 
                                     // Display the correct fragments.
@@ -500,16 +519,16 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                                             deleteMenuItem.setIcon(R.drawable.delete_day);
                                         }
                                     } else {  // The device in in one-paned mode.
-                                        // Display `domainSettingsFragment`.
+                                        // Display the domain settings fragment.
                                         fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
 
                                         // Hide the add domain floating action button.
                                         addDomainFAB.hide();
 
-                                        // Show and enable `deleteMenuItem`.
+                                        // Show and enable the delete menu item.
                                         deleteMenuItem.setVisible(true);
 
-                                        // Display `domainSettingsFragment`.
+                                        // Display the domain settings fragment.
                                         fragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
                                     }
                                 } else {  // The snackbar was dismissed without the undo button being pushed.
@@ -567,21 +586,40 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
     }
 
     @Override
-    protected void onSaveInstanceState(@NonNull Bundle outState) {
-        // Store the current `DomainSettingsFragment` state in `outState`.
-        if (findViewById(R.id.domain_settings_scrollview) != null) {  // `DomainSettingsFragment` is displayed.
+    protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Get a handle for the domain settings scrollview.
+        ScrollView domainSettingsScrollView = findViewById(R.id.domain_settings_scrollview);
+
+        // Check to see if the domain settings scrollview exists.
+        if (domainSettingsScrollView == null) {  // The domain settings are not displayed.
+            // Store the domain settings status in the bundle.
+            savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false);
+            savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1);
+            savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0);
+        } else {  // The domain settings are displayed.
             // Save any changes that have been made to the domain settings.
             saveDomainSettings(coordinatorLayout, resources);
 
-            // Store `DomainSettingsDisplayed`.
-            outState.putBoolean("domain_settings_displayed", true);
-            outState.putInt("domain_settings_database_id", DomainSettingsFragment.databaseId);
-        } else {  // `DomainSettingsFragment` is not displayed.
-            outState.putBoolean("domain_settings_displayed", false);
-            outState.putInt("domain_settings_database_id", -1);
+            // Get the domain settings scroll Y.
+            int domainSettingsScrollY = domainSettingsScrollView.getScrollY();
+
+            // Store the domain settings status in the bundle.
+            savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true);
+            savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId);
+            savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY);
         }
 
-        super.onSaveInstanceState(outState);
+        // Check to see if the domains listview exists.
+        if (domainsListView != null) {
+            // Get the domains listview position.
+            int domainsListViewPosition = domainsListView.getFirstVisiblePosition();
+
+            // Store the listview position in the bundle.
+            savedInstanceState.putInt(LISTVIEW_POSITION, domainsListViewPosition);
+        }
     }
 
     // Control what the navigation bar back button does.
@@ -623,7 +661,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
             fragmentManager.executePendingTransactions();
 
             // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
-            populateDomainsListView(-1);
+            populateDomainsListView(-1, domainsListViewPosition);
 
             // Show the add domain floating action button.
             addDomainFAB.show();
@@ -666,20 +704,25 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
 
         // Display the newly created domain.
         if (twoPanedMode) {  // The device in in two-paned mode.
-            populateDomainsListView(currentDomainDatabaseId);
+            populateDomainsListView(currentDomainDatabaseId, 0);
         } else {  // The device is in single-paned mode.
             // Hide the add domain floating action button.
             addDomainFAB.hide();
 
-            // Show and enable `deleteMenuItem`.
+            // Show and enable the delete menu item.
             DomainsActivity.deleteMenuItem.setVisible(true);
 
-            // Add the current domain database ID to the arguments bundle.
+            // Create an arguments bundle.
             Bundle argumentsBundle = new Bundle();
+
+            // Add the domain settings to the arguments bundle.  The scroll Y should always be `0` on a new domain.
             argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
+            argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, 0);
 
-            // Add and arguments bundle to the domain setting fragment.
+            // Instantiate a new domain settings fragment.
             DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
+
+            // Add the arguments bundle to the domain setting fragment.
             domainSettingsFragment.setArguments(argumentsBundle);
 
             // Display the domain settings fragment.
@@ -690,18 +733,18 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
     public void saveDomainSettings(View view, Resources resources) {
         // Get handles for the domain settings.
         EditText domainNameEditText = view.findViewById(R.id.domain_settings_name_edittext);
-        Switch javaScriptSwitch = view.findViewById(R.id.javascript_switch);
-        Switch firstPartyCookiesSwitch = view.findViewById(R.id.first_party_cookies_switch);
-        Switch thirdPartyCookiesSwitch = view.findViewById(R.id.third_party_cookies_switch);
-        Switch domStorageSwitch = view.findViewById(R.id.dom_storage_switch);
-        Switch formDataSwitch = view.findViewById(R.id.form_data_switch);  // Form data can be removed once the minimum API >= 26.
-        Switch easyListSwitch = view.findViewById(R.id.easylist_switch);
-        Switch easyPrivacySwitch = view.findViewById(R.id.easyprivacy_switch);
-        Switch fanboysAnnoyanceSwitch = view.findViewById(R.id.fanboys_annoyance_list_switch);
-        Switch fanboysSocialBlockingSwitch = view.findViewById(R.id.fanboys_social_blocking_list_switch);
-        Switch ultraListSwitch = view.findViewById(R.id.ultralist_switch);
-        Switch ultraPrivacySwitch = view.findViewById(R.id.ultraprivacy_switch);
-        Switch blockAllThirdPartyRequestsSwitch = view.findViewById(R.id.block_all_third_party_requests_switch);
+        SwitchCompat javaScriptSwitch = view.findViewById(R.id.javascript_switch);
+        SwitchCompat firstPartyCookiesSwitch = view.findViewById(R.id.first_party_cookies_switch);
+        SwitchCompat thirdPartyCookiesSwitch = view.findViewById(R.id.third_party_cookies_switch);
+        SwitchCompat domStorageSwitch = view.findViewById(R.id.dom_storage_switch);
+        SwitchCompat formDataSwitch = view.findViewById(R.id.form_data_switch);  // Form data can be removed once the minimum API >= 26.
+        SwitchCompat easyListSwitch = view.findViewById(R.id.easylist_switch);
+        SwitchCompat easyPrivacySwitch = view.findViewById(R.id.easyprivacy_switch);
+        SwitchCompat fanboysAnnoyanceSwitch = view.findViewById(R.id.fanboys_annoyance_list_switch);
+        SwitchCompat fanboysSocialBlockingSwitch = view.findViewById(R.id.fanboys_social_blocking_list_switch);
+        SwitchCompat ultraListSwitch = view.findViewById(R.id.ultralist_switch);
+        SwitchCompat ultraPrivacySwitch = view.findViewById(R.id.ultraprivacy_switch);
+        SwitchCompat blockAllThirdPartyRequestsSwitch = view.findViewById(R.id.block_all_third_party_requests_switch);
         Spinner userAgentSpinner = view.findViewById(R.id.user_agent_spinner);
         EditText customUserAgentEditText = view.findViewById(R.id.custom_user_agent_edittext);
         Spinner fontSizeSpinner = view.findViewById(R.id.font_size_spinner);
@@ -710,9 +753,9 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         Spinner webViewThemeSpinner = view.findViewById(R.id.webview_theme_spinner);
         Spinner wideViewportSpinner = view.findViewById(R.id.wide_viewport_spinner);
         Spinner displayWebpageImagesSpinner = view.findViewById(R.id.display_webpage_images_spinner);
-        Switch pinnedSslCertificateSwitch = view.findViewById(R.id.pinned_ssl_certificate_switch);
+        SwitchCompat pinnedSslCertificateSwitch = view.findViewById(R.id.pinned_ssl_certificate_switch);
         RadioButton currentWebsiteCertificateRadioButton = view.findViewById(R.id.current_website_certificate_radiobutton);
-        Switch pinnedIpAddressesSwitch = view.findViewById(R.id.pinned_ip_addresses_switch);
+        SwitchCompat pinnedIpAddressesSwitch = view.findViewById(R.id.pinned_ip_addresses_switch);
         RadioButton currentIpAddressesRadioButton = view.findViewById(R.id.current_ip_addresses_radiobutton);
 
         // Extract the data for the domain settings.
@@ -789,7 +832,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         }
     }
 
-    private void populateDomainsListView(final int highlightedDomainDatabaseId) {
+    private void populateDomainsListView(final int highlightedDomainDatabaseId, int domainsListViewPosition) {
         // get a handle for the current `domains_listview`.
         domainsListView = findViewById(R.id.domains_listview);
 
@@ -816,6 +859,9 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         // Update the list view.
         domainsListView.setAdapter(domainsCursorAdapter);
 
+        // Restore the scroll position.
+        domainsListView.setSelection(domainsListViewPosition);
+
         // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain.
         if (DomainsActivity.twoPanedMode && (domainsCursor.getCount() > 0)) {  // Two-paned mode is enabled and there is at least one domain.
             // Initialize `highlightedDomainPosition`.
@@ -842,12 +888,17 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
             domainsCursor.moveToPosition(highlightedDomainPosition);
             currentDomainDatabaseId = domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper._ID));
 
-            // Store the database ID in the arguments bundle.
+            // Create an arguments bundle.
             Bundle argumentsBundle = new Bundle();
+
+            // Store the domain settings in the arguments bundle.
             argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentDomainDatabaseId);
+            argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY);
 
-            // Add and arguments bundle to the domain settings fragment.
+            // Instantiate a new domain settings fragment.
             DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
+
+            // Add the arguments bundle to the domain settings fragment.
             domainSettingsFragment.setArguments(argumentsBundle);
 
             // Display the domain settings fragment.
index cc9363f9ad155b74be6cd341bad82094052db3c4..53c1109c726c7c520c56d35aa83b5bad32c6b550 100644 (file)
@@ -79,16 +79,41 @@ import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
 public class ImportExportActivity extends AppCompatActivity implements StoragePermissionDialog.StoragePermissionDialogListener {
-    // Create the encryption constants.
+    // Define the encryption constants.
     private final int NO_ENCRYPTION = 0;
     private final int PASSWORD_ENCRYPTION = 1;
     private final int OPENPGP_ENCRYPTION = 2;
 
-    // Create the activity result constants.
+    // Define the activity result constants.
     private final int BROWSE_RESULT_CODE = 0;
     private final int OPENPGP_EXPORT_RESULT_CODE = 1;
 
-    // `openKeychainInstalled` is accessed from an inner class.
+    // Define the saved instance state constants.
+    private final String PASSWORD_ENCRYPTED_TEXTINPUTLAYOUT_VISIBILITY = "password_encrypted_textinputlayout_visibility";
+    private final String KITKAT_PASSWORD_ENCRYPTED_TEXTVIEW_VISIBILITY = "kitkat_password_encrypted_textview_visibility";
+    private final String OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY = "open_keychain_required_textview_visibility";
+    private final String FILE_LOCATION_CARD_VIEW = "file_location_card_view";
+    private final String FILE_NAME_LINEARLAYOUT_VISIBILITY = "file_name_linearlayout_visibility";
+    private final String FILE_DOES_NOT_EXIST_TEXTVIEW_VISIBILITY = "file_does_not_exist_textview_visibility";
+    private final String FILE_EXISTS_WARNING_TEXTVIEW_VISIBILITY = "file_exists_warning_textview_visibility";
+    private final String OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY = "open_keychain_import_instructions_textview_visibility";
+    private final String IMPORT_EXPORT_BUTTON_VISIBILITY = "import_export_button_visibility";
+    private final String FILE_NAME_TEXT = "file_name_text";
+    private final String IMPORT_EXPORT_BUTTON_TEXT = "import_export_button_text";
+
+    // Define the class views.
+    TextInputLayout passwordEncryptionTextInputLayout;
+    TextView kitKatPasswordEncryptionTextView;
+    TextView openKeychainRequiredTextView;
+    CardView fileLocationCardView;
+    LinearLayout fileNameLinearLayout;
+    EditText fileNameEditText;
+    TextView fileDoesNotExistTextView;
+    TextView fileExistsWarningTextView;
+    TextView openKeychainImportInstructionsTextView;
+    Button importExportButton;
+
+    // Define the class variables.
     private boolean openKeychainInstalled;
 
     @Override
@@ -138,19 +163,19 @@ public class ImportExportActivity extends AppCompatActivity implements StoragePe
 
         // Get handles for the views that need to be modified.
         Spinner encryptionSpinner = findViewById(R.id.encryption_spinner);
-        TextInputLayout passwordEncryptionTextInputLayout = findViewById(R.id.password_encryption_textinputlayout);
+        passwordEncryptionTextInputLayout = findViewById(R.id.password_encryption_textinputlayout);
         EditText encryptionPasswordEditText = findViewById(R.id.password_encryption_edittext);
-        TextView kitKatPasswordEncryptionTextView = findViewById(R.id.kitkat_password_encryption_textview);
-        TextView openKeychainRequiredTextView = findViewById(R.id.openkeychain_required_textview);
-        CardView fileLocationCardView = findViewById(R.id.file_location_cardview);
+        kitKatPasswordEncryptionTextView = findViewById(R.id.kitkat_password_encryption_textview);
+        openKeychainRequiredTextView = findViewById(R.id.openkeychain_required_textview);
+        fileLocationCardView = findViewById(R.id.file_location_cardview);
         RadioButton importRadioButton = findViewById(R.id.import_radiobutton);
         RadioButton exportRadioButton = findViewById(R.id.export_radiobutton);
-        LinearLayout fileNameLinearLayout = findViewById(R.id.file_name_linearlayout);
-        EditText fileNameEditText = findViewById(R.id.file_name_edittext);
-        TextView fileDoesNotExistTextView = findViewById(R.id.file_does_not_exist_textview);
-        TextView fileExistsWarningTextView = findViewById(R.id.file_exists_warning_textview);
-        TextView openKeychainImportInstructionsTextView = findViewById(R.id.openkeychain_import_instructions_textview);
-        Button importExportButton = findViewById(R.id.import_export_button);
+        fileNameLinearLayout = findViewById(R.id.file_name_linearlayout);
+        fileNameEditText = findViewById(R.id.file_name_edittext);
+        fileDoesNotExistTextView = findViewById(R.id.file_does_not_exist_textview);
+        fileExistsWarningTextView = findViewById(R.id.file_exists_warning_textview);
+        openKeychainImportInstructionsTextView = findViewById(R.id.openkeychain_import_instructions_textview);
+        importExportButton = findViewById(R.id.import_export_button);
         TextView storagePermissionTextView = findViewById(R.id.import_export_storage_permission_textview);
 
         // Create an array adapter for the spinner.
@@ -162,16 +187,6 @@ public class ImportExportActivity extends AppCompatActivity implements StoragePe
         // Set the array adapter for the spinner.
         encryptionSpinner.setAdapter(encryptionArrayAdapter);
 
-        // Initially hide the unneeded views.
-        passwordEncryptionTextInputLayout.setVisibility(View.GONE);
-        kitKatPasswordEncryptionTextView.setVisibility(View.GONE);
-        openKeychainRequiredTextView.setVisibility(View.GONE);
-        fileNameLinearLayout.setVisibility(View.GONE);
-        fileDoesNotExistTextView.setVisibility(View.GONE);
-        fileExistsWarningTextView.setVisibility(View.GONE);
-        openKeychainImportInstructionsTextView.setVisibility(View.GONE);
-        importExportButton.setVisibility(View.GONE);
-
         // Instantiate the download location helper.
         DownloadLocationHelper downloadLocationHelper = new DownloadLocationHelper();
 
@@ -460,6 +475,55 @@ public class ImportExportActivity extends AppCompatActivity implements StoragePe
                 }
             }
         });
+
+        // Check to see if the activity has been restarted.
+        if (savedInstanceState == null) {  // The app has not been restarted.
+            // Initially hide the unneeded views.
+            passwordEncryptionTextInputLayout.setVisibility(View.GONE);
+            kitKatPasswordEncryptionTextView.setVisibility(View.GONE);
+            openKeychainRequiredTextView.setVisibility(View.GONE);
+            fileNameLinearLayout.setVisibility(View.GONE);
+            fileDoesNotExistTextView.setVisibility(View.GONE);
+            fileExistsWarningTextView.setVisibility(View.GONE);
+            openKeychainImportInstructionsTextView.setVisibility(View.GONE);
+            importExportButton.setVisibility(View.GONE);
+        } else {  // The app has been restarted.
+            // Restore the visibility of the views.
+            passwordEncryptionTextInputLayout.setVisibility(savedInstanceState.getInt(PASSWORD_ENCRYPTED_TEXTINPUTLAYOUT_VISIBILITY));
+            kitKatPasswordEncryptionTextView.setVisibility(savedInstanceState.getInt(KITKAT_PASSWORD_ENCRYPTED_TEXTVIEW_VISIBILITY));
+            openKeychainRequiredTextView.setVisibility(savedInstanceState.getInt(OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY));
+            fileLocationCardView.setVisibility(savedInstanceState.getInt(FILE_LOCATION_CARD_VIEW));
+            fileNameLinearLayout.setVisibility(savedInstanceState.getInt(FILE_NAME_LINEARLAYOUT_VISIBILITY));
+            fileDoesNotExistTextView.setVisibility(savedInstanceState.getInt(FILE_DOES_NOT_EXIST_TEXTVIEW_VISIBILITY));
+            fileExistsWarningTextView.setVisibility(savedInstanceState.getInt(FILE_EXISTS_WARNING_TEXTVIEW_VISIBILITY));
+            openKeychainImportInstructionsTextView.setVisibility(savedInstanceState.getInt(OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY));
+            importExportButton.setVisibility(savedInstanceState.getInt(IMPORT_EXPORT_BUTTON_VISIBILITY));
+
+            // Restore the text.
+            fileNameEditText.post(() -> fileNameEditText.setText(savedInstanceState.getString(FILE_NAME_TEXT)));
+            importExportButton.setText(savedInstanceState.getString(IMPORT_EXPORT_BUTTON_TEXT));
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState (@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Save the visibility of the views.
+        savedInstanceState.putInt(PASSWORD_ENCRYPTED_TEXTINPUTLAYOUT_VISIBILITY, passwordEncryptionTextInputLayout.getVisibility());
+        savedInstanceState.putInt(KITKAT_PASSWORD_ENCRYPTED_TEXTVIEW_VISIBILITY, kitKatPasswordEncryptionTextView.getVisibility());
+        savedInstanceState.putInt(OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY, openKeychainRequiredTextView.getVisibility());
+        savedInstanceState.putInt(FILE_LOCATION_CARD_VIEW, fileLocationCardView.getVisibility());
+        savedInstanceState.putInt(FILE_NAME_LINEARLAYOUT_VISIBILITY, fileNameLinearLayout.getVisibility());
+        savedInstanceState.putInt(FILE_DOES_NOT_EXIST_TEXTVIEW_VISIBILITY, fileDoesNotExistTextView.getVisibility());
+        savedInstanceState.putInt(FILE_EXISTS_WARNING_TEXTVIEW_VISIBILITY, fileExistsWarningTextView.getVisibility());
+        savedInstanceState.putInt(OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY, openKeychainImportInstructionsTextView.getVisibility());
+        savedInstanceState.putInt(IMPORT_EXPORT_BUTTON_VISIBILITY, importExportButton.getVisibility());
+
+        // Save the text.
+        savedInstanceState.putString(FILE_NAME_TEXT, fileNameEditText.getText().toString());
+        savedInstanceState.putString(IMPORT_EXPORT_BUTTON_TEXT, importExportButton.getText().toString());
     }
 
     public void onClickRadioButton(View view) {
index de0b873d8afa276757553de795f286c3bf3ed16c..7878f985ab5609841867b0554e966a2df869d132 100644 (file)
@@ -30,7 +30,6 @@ import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.media.MediaScannerConnection;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
 import android.util.TypedValue;
@@ -39,6 +38,7 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.EditText;
+import android.widget.ScrollView;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -53,6 +53,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import com.google.android.material.snackbar.Snackbar;
 
 import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.asynctasks.GetLogcat;
 import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
 import com.stoutner.privacybrowser.dialogs.SaveLogcatDialog;
 import com.stoutner.privacybrowser.helpers.FileNameHelper;
@@ -66,12 +67,18 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
-import java.lang.ref.WeakReference;
 import java.nio.charset.StandardCharsets;
 
 public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialog.SaveLogcatListener, StoragePermissionDialog.StoragePermissionDialogListener {
+    // Initialize the saved instance state constants.
+    private final String SCROLLVIEW_POSITION = "scrollview_position";
+
+    // Define the class variables.
     private String filePathString;
 
+    // Define the class views.
+    private TextView logcatTextView;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         // Get a handle for the shared preferences.
@@ -107,11 +114,14 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo
         // Display the the back arrow in the action bar.
         actionBar.setDisplayHomeAsUpEnabled(true);
 
+        // Populate the class views.
+        logcatTextView = findViewById(R.id.logcat_textview);
+
         // Implement swipe to refresh.
         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.logcat_swiperefreshlayout);
         swipeRefreshLayout.setOnRefreshListener(() -> {
             // Get the current logcat.
-            new GetLogcat(this).execute();
+            new GetLogcat(this, 0).execute();
         });
 
         // Get the current theme status.
@@ -136,8 +146,17 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo
         // Set the swipe refresh background color.
         swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
 
+        // Initialize the scrollview Y position int.
+        int scrollViewYPositionInt = 0;
+
+        // Check to see if the activity has been restarted.
+        if (savedInstanceState != null) {
+            // Get the saved scrollview position.
+            scrollViewYPositionInt = savedInstanceState.getInt(SCROLLVIEW_POSITION);
+        }
+
         // Get the logcat.
-        new GetLogcat(this).execute();
+        new GetLogcat(this, scrollViewYPositionInt).execute();
     }
 
     @Override
@@ -160,9 +179,6 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo
                 // Get a handle for the clipboard manager.
                 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
 
-                // Get a handle for the logcat text view.
-                TextView logcatTextView = findViewById(R.id.logcat_textview);
-
                 // Save the logcat in a ClipData.
                 ClipData logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.getText());
 
@@ -197,7 +213,7 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo
                     process.waitFor();
 
                     // Reload the logcat.
-                    new GetLogcat(this).execute();
+                    new GetLogcat(this, 0).execute();
                 } catch (IOException|InterruptedException exception) {
                     // Do nothing.
                 }
@@ -211,6 +227,21 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo
         }
     }
 
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Get a handle for the logcat scrollview.
+        ScrollView logcatScrollView = findViewById(R.id.logcat_scrollview);
+
+        // Get the scrollview Y position.
+        int scrollViewYPositionInt = logcatScrollView.getScrollY();
+
+        // Store the scrollview Y position in the bundle.
+        savedInstanceState.putInt(SCROLLVIEW_POSITION, scrollViewYPositionInt);
+    }
+
     @Override
     public void onSaveLogcat(DialogFragment dialogFragment) {
         // Get a handle for the dialog fragment.
@@ -273,18 +304,12 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo
             // Save the logcat.
             saveLogcat(filePathString);
         } else {  // The storage permission was not granted.
-            // Get a handle for the logcat text view.
-            TextView logcatTextView = findViewById(R.id.logcat_textview);
-
             // Display an error snackbar.
             Snackbar.make(logcatTextView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
         }
     }
 
     private void saveLogcat(String fileNameString) {
-        // Get a handle for the logcat text view.
-        TextView logcatTextView = findViewById(R.id.logcat_textview);
-
         try {
             // Get the logcat as a string.
             String logcatString = logcatTextView.getText().toString();
@@ -374,79 +399,4 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo
             }
         }
     }
-
-    // `Void` does not declare any parameters.  `Void` does not declare progress units.  `String` contains the results.
-    private static class GetLogcat extends AsyncTask<Void, Void, String> {
-        // Create a weak reference to the calling activity.
-        private final WeakReference<Activity> activityWeakReference;
-
-        // Populate the weak reference to the calling activity.
-        GetLogcat(Activity activity) {
-            activityWeakReference = new WeakReference<>(activity);
-        }
-
-        @Override
-        protected String doInBackground(Void... parameters) {
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
-
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                return "";
-            }
-
-            // Create a log string builder.
-            StringBuilder logStringBuilder = new StringBuilder();
-
-            try {
-                // Get the logcat.  `-b all` gets all the buffers (instead of just crash, main, and system).  `-v long` produces more complete information.  `-d` dumps the logcat and exits.
-                Process process = Runtime.getRuntime().exec("logcat -b all -v long -d");
-
-                // Wrap the logcat in a buffered reader.
-                BufferedReader logBufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
-
-                // Create a log transfer string.
-                String logTransferString;
-
-                // Use the log transfer string to copy the logcat from the buffered reader to the string builder.
-                while ((logTransferString = logBufferedReader.readLine()) != null) {
-                    // Append a line.
-                    logStringBuilder.append(logTransferString);
-
-                    // Append a line break.
-                    logStringBuilder.append("\n");
-                }
-
-                // Close the buffered reader.
-                logBufferedReader.close();
-            } catch (IOException exception) {
-                // Do nothing.
-            }
-
-            // Return the logcat.
-            return logStringBuilder.toString();
-        }
-
-        // `onPostExecute()` operates on the UI thread.
-        @Override
-        protected void onPostExecute(String logcatString) {
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
-
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                return;
-            }
-
-            // Get handles for the views.
-            TextView logcatTextView = activity.findViewById(R.id.logcat_textview);
-            SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.logcat_swiperefreshlayout);
-
-            // Display the logcat.
-            logcatTextView.setText(logcatString);
-
-            // Stop the swipe to refresh animation if it is displayed.
-            swipeRefreshLayout.setRefreshing(false);
-        }
-    }
 }
\ No newline at end of file
index c130a2671dd550e4b62943a0ac66d1029b5818f4..4280c56afc41168110921900cc448b750d211c26 100644 (file)
@@ -102,6 +102,7 @@ import androidx.appcompat.widget.Toolbar;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 import androidx.core.app.ActivityCompat;
 import androidx.core.content.ContextCompat;
+import androidx.core.content.res.ResourcesCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.DialogFragment;
@@ -168,12 +169,17 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
         EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
         PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
         UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
 
+    // The executor service handles background tasks.  It is accessed from `ViewSourceActivity`.  TODO.  Change the number of threads, or create a single thread executor.
+    public static ExecutorService executorService = Executors.newFixedThreadPool(4);
+
     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
     public static String orbotStatus = "unknown";
 
@@ -216,6 +222,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private final int PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE = 2;
     private final int PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE = 3;
 
+    // Define the saved instance state constants.
+    private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
+    private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
+    private final String SAVED_TAB_POSITION = "saved_tab_position";
+    private final String PROXY_MODE = "proxy_mode";
+
+    // Define the saved instance state variables.
+    private ArrayList<Bundle> savedStateArrayList;
+    private ArrayList<Bundle> savedNestedScrollWebViewStateArrayList;
+    private int savedTabPosition;
+    private String savedProxyMode;
+
+    // Define the class views.
+    private AppBarLayout appBarLayout;
+    private TabLayout tabLayout;
+    private ViewPager webViewPager;
+
+    // Define the class variables.
+    private String newIntentUrl;
+
     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
     private NestedScrollWebView currentWebView;
@@ -317,6 +343,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the default commands.
         super.onCreate(savedInstanceState);
 
+        // Check to see if the activity has been restarted.
+        if (savedInstanceState != null) {
+            // Store the saved instance state variables.
+            savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
+            savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
+            savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
+            savedProxyMode = savedInstanceState.getString(PROXY_MODE);
+        }
+
         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
 
@@ -363,10 +398,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the content view.
         setContentView(R.layout.main_framelayout);
 
-        // Get handles for the views that need to be modified.
+        // Get handles for the views.
         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+        appBarLayout = findViewById(R.id.appbar_layout);
         Toolbar toolbar = findViewById(R.id.toolbar);
-        ViewPager webViewPager = findViewById(R.id.webviewpager);
+        tabLayout = findViewById(R.id.tablayout);
+        webViewPager = findViewById(R.id.webviewpager);
 
         // Get a handle for the app compat delegate.
         AppCompatDelegate appCompatDelegate = getDelegate();
@@ -399,6 +436,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Store up to 100 tabs in memory.
         webViewPager.setOffscreenPageLimit(100);
 
+        // Initialize the app.
+        initializeApp();
+
+        // Apply the app settings from the shared preferences.
+        applyAppSettings();
+
         // Populate the blocklists.
         new PopulateBlocklists(this, this).execute();
     }
@@ -447,16 +490,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     url = intentUriData.toString();
                 }
 
-                // Add a new tab if specified in the preferences.
-                if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
-                    // Set the loading new intent flag.
-                    loadingNewIntent = true;
-
-                    // Add a new tab.
-                    addNewTab(url, true);
-                } else {  // Load the URL in the current tab.
-                    // Make it so.
-                    loadUrl(currentWebView, url);
+                // Check to see if the app is in the process of restarting
+                if (savedStateArrayList == null) {  // The app is not in the process of being restarted.  Process the new intent.
+                    // Add a new tab if specified in the preferences.
+                    if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
+                        // Set the loading new intent flag.
+                        loadingNewIntent = true;
+
+                        // Add a new tab.
+                        addNewTab(url, true);
+                    } else {  // Load the URL in the current tab.
+                        // Make it so.
+                        loadUrl(currentWebView, url);
+                    }
+                } else {  // The app is being restarted.  Store the URL, which will be processed in `finishedPopulatingBlocklists()`.
+                    newIntentUrl = url;
                 }
 
                 // Get a handle for the drawer layout.
@@ -629,6 +677,50 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Create the saved state array lists.
+        ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
+        ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
+
+        // Get the URLs from each tab.
+        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+            // Get the WebView tab fragment.
+            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
+
+            if (fragmentView != null) {
+                // Get the nested scroll WebView from the tab fragment.
+                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+
+                // Create saved state bundle.
+                Bundle savedStateBundle = new Bundle();
+
+                // Get the current states.
+                nestedScrollWebView.saveState(savedStateBundle);
+                Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
+
+                // Store the saved states in the array lists.
+                savedStateArrayList.add(savedStateBundle);
+                savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
+            }
+        }
+
+        // Get the current tab position.
+        int currentTabPosition = tabLayout.getSelectedTabPosition();
+
+        // Store the saved states in the bundle.
+        savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
+        savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
+        savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
+        savedInstanceState.putString(PROXY_MODE, proxyMode);
+    }
+
     @Override
     public void onDestroy() {
         // Unregister the orbot status broadcast receiver if it exists.
@@ -715,12 +807,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
                 // Set the icon according to the current theme status.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    // Set the dark stop icon.
-                    refreshMenuItem.setIcon(R.drawable.close_night);
-                } else {
-                    // Set the light stop icon.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                     refreshMenuItem.setIcon(R.drawable.close_day);
+                } else {
+                    refreshMenuItem.setIcon(R.drawable.close_night);
                 }
             }
         }
@@ -1014,11 +1104,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Display a `Snackbar`.
                 if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
                 } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
                 } else {  // Privacy mode.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
                 }
 
                 // Reload the current WebView.
@@ -1064,11 +1154,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Display a snackbar.
                 if (cookieManager.acceptCookie()) {  // First-party cookies are enabled.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
                 } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
                 } else {  // Privacy mode.
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
                 }
 
                 // Reload the current WebView.
@@ -1087,9 +1177,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     // Display a snackbar.
                     if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
-                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+                        Snackbar.make(webViewPager, R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
                     } else {
-                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+                        Snackbar.make(webViewPager, R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
                     }
 
                     // Reload the current WebView.
@@ -1111,9 +1201,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Display a snackbar.
                 if (currentWebView.getSettings().getDomStorageEnabled()) {
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
                 } else {
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
                 }
 
                 // Reload the current WebView.
@@ -1132,9 +1222,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Display a snackbar.
                 if (currentWebView.getSettings().getSaveFormData()) {
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
                 } else {
-                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
+                    Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
                 }
 
                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
@@ -1147,7 +1237,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 return true;
 
             case R.id.clear_cookies:
-                Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
+                Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
                         .setAction(R.string.undo, v -> {
                             // Do nothing because everything will be handled by `onDismissed()` below.
                         })
@@ -1171,7 +1261,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 return true;
 
             case R.id.clear_dom_storage:
-                Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
+                Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
                         .setAction(R.string.undo, v -> {
                             // Do nothing because everything will be handled by `onDismissed()` below.
                         })
@@ -1229,7 +1319,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Form data can be remove once the minimum API >= 26.
             case R.id.clear_form_data:
-                Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
+                Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
                         .setAction(R.string.undo, v -> {
                             // Do nothing because everything will be handled by `onDismissed()` below.
                         })
@@ -2560,9 +2650,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // Override `onBackPressed` to handle the navigation drawer and and the WebViews.
     @Override
     public void onBackPressed() {
-        // Get a handle for the drawer layout and the tab layout.
+        // Get a handle for the drawer layout.
         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-        TabLayout tabLayout = findViewById(R.id.tablayout);
 
         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
             // Close the navigation drawer.
@@ -3130,160 +3219,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
-    private void applyAppSettings() {
-        // Initialize the app if this is the first run.  This is done here instead of in `onCreate()` to shorten the time that an unthemed background is displayed on app startup.
-        if (webViewDefaultUserAgent == null) {
-            initializeApp();
-        }
-
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
-
-        // Store the values from the shared preferences in variables.
-        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
-        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
-        sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
-        sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
-        sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
-        proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
-        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
-        hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
-        scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
-
-        // Get the search string.
-        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
-
-        // Set the search string.
-        if (searchString.equals("Custom URL")) {  // A custom search string is used.
-            searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
-        } else {  // A custom search string is not used.
-            searchURL = searchString;
-        }
-
-        // Get a handle for the app compat delegate.
-        AppCompatDelegate appCompatDelegate = getDelegate();
-
-        // Get handles for the views that need to be modified.
-        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-        AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
-        ActionBar actionBar = appCompatDelegate.getSupportActionBar();
-        Toolbar toolbar = findViewById(R.id.toolbar);
-        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
-        LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
-        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-
-        // Remove the incorrect lint warning below that the action bar might be null.
-        assert actionBar != null;
-
-        // Apply the proxy.
-        applyProxy(false);
-
-        // Set Do Not Track status.
-        if (doNotTrackEnabled) {
-            customHeaders.put("DNT", "1");
-        } else {
-            customHeaders.remove("DNT");
-        }
-
-        // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
-        CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-        AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
-        AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
-        AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
-
-        // Add the scrolling behavior to the layout parameters.
-        if (scrollAppBar) {
-            // Enable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
-            toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-        } else {
-            // Disable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(null);
-            toolbarLayoutParams.setScrollFlags(0);
-            findOnPageLayoutParams.setScrollFlags(0);
-            tabsLayoutParams.setScrollFlags(0);
-
-            // Expand the app bar if it is currently collapsed.
-            appBarLayout.setExpanded(true);
-        }
-
-        // Apply the modified layout parameters.
-        swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
-        toolbar.setLayoutParams(toolbarLayoutParams);
-        findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
-        tabsLinearLayout.setLayoutParams(tabsLayoutParams);
-
-        // Set the app bar scrolling for each WebView.
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
-
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
-
-            // Only modify the WebViews if they exist.
-            if (fragmentView != null) {
-                // Get the nested scroll WebView from the tab fragment.
-                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
-
-                // Set the app bar scrolling.
-                nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
-            }
-        }
-
-        // Update the full screen browsing mode settings.
-        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-            // Update the visibility of the app bar, which might have changed in the settings.
-            if (hideAppBar) {
-                // Hide the tab linear layout.
-                tabsLinearLayout.setVisibility(View.GONE);
-
-                // Hide the action bar.
-                actionBar.hide();
-            } else {
-                // Show the tab linear layout.
-                tabsLinearLayout.setVisibility(View.VISIBLE);
-
-                // Show the action bar.
-                actionBar.show();
-            }
-
-            // Hide the banner ad in the free flavor.
-            if (BuildConfig.FLAVOR.contentEquals("free")) {
-                AdHelper.hideAd(findViewById(R.id.adview));
-            }
-
-            /* Hide the system bars.
-             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-             */
-            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-        } else {  // Privacy Browser is not in full screen browsing mode.
-            // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
-            inFullScreenBrowsingMode = false;
-
-            // Show the tab linear layout.
-            tabsLinearLayout.setVisibility(View.VISIBLE);
-
-            // Show the action bar.
-            actionBar.show();
-
-            // Show the banner ad in the free flavor.
-            if (BuildConfig.FLAVOR.contentEquals("free")) {
-                // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
-                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
-            }
-
-            // Remove the `SYSTEM_UI` flags from the root frame layout.
-            rootFrameLayout.setSystemUiVisibility(0);
-        }
-    }
-
     private void initializeApp() {
         // Get a handle for the input method.
         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -3291,11 +3226,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Remove the lint warning below that the input method manager might be null.
         assert inputMethodManager != null;
 
-        // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
-        redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+        // Initialize the gray foreground color spans for highlighting the URLs.  The deprecated `getResources()` must be used until API >= 23.
         initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
         finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
 
+        // Get the current theme status.
+        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+        // Set the red color span according to the theme.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+        } else {
+            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
+        }
+
         // Get handles for the URL views.
         EditText urlEditText = findViewById(R.id.url_edittext);
 
@@ -3389,9 +3333,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Get handles for views that need to be modified.
         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
         NavigationView navigationView = findViewById(R.id.navigationview);
-        TabLayout tabLayout = findViewById(R.id.tablayout);
         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-        ViewPager webViewPager = findViewById(R.id.webviewpager);
         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
@@ -3401,12 +3343,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Listen for touches on the navigation menu.
         navigationView.setNavigationItemSelectedListener(this);
 
-        // Get handles for the navigation menu and the back and forward menu items.  The menu is 0 based.
+        // Get handles for the navigation menu and the back and forward menu items.
         Menu navigationMenu = navigationView.getMenu();
-        MenuItem navigationBackMenuItem = navigationMenu.getItem(2);
-        MenuItem navigationForwardMenuItem = navigationMenu.getItem(3);
-        MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4);
-        MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
+        MenuItem navigationBackMenuItem = navigationMenu.findItem(R.id.back);
+        MenuItem navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
+        MenuItem navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
+        MenuItem navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
 
         // Update the web view pager every time a tab is modified.
         webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@@ -3423,7 +3365,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Set the current WebView.
                 setCurrentWebView(position);
 
-                // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled via swiping in the view pager or by creating a new tab.
+                // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled by creating a new tab.
                 if (tabLayout.getSelectedTabPosition() != position) {
                     // Create a handler to select the tab.
                     Handler selectTabHandler = new Handler();
@@ -3440,7 +3382,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         tab.select();
                     };
 
-                    // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated.
+                    // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated.  TODO.  Switch to a post command.
                     selectTabHandler.postDelayed(selectTabRunnable, 150);
                 }
             }
@@ -3561,14 +3503,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
         defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
 
-        // Get the current theme status.
-        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
         // Set the refresh color scheme according to the theme.
-        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-            swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
-        } else {
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
             swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
+        } else {
+            swipeRefreshLayout.setColorSchemeResources(R.color.violet_500);
         }
 
         // Initialize a color background typed value.
@@ -3707,6 +3646,163 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         bareWebView.destroy();
     }
 
+    private void applyAppSettings() {
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+        // Store the values from the shared preferences in variables.
+        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
+        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
+        sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
+        sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
+        sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
+        proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
+        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
+        hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
+        scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
+
+        // Apply the saved proxy mode if the app has been restarted.
+        if (savedProxyMode != null) {
+            // Apply the saved proxy mode.
+            proxyMode = savedProxyMode;
+
+            // Reset the saved proxy mode.
+            savedProxyMode = null;
+        }
+
+        // Get the search string.
+        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
+
+        // Set the search string.
+        if (searchString.equals("Custom URL")) {  // A custom search string is used.
+            searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
+        } else {  // A custom search string is not used.
+            searchURL = searchString;
+        }
+
+        // Get a handle for the app compat delegate.
+        AppCompatDelegate appCompatDelegate = getDelegate();
+
+        // Get handles for the views that need to be modified.
+        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+        ActionBar actionBar = appCompatDelegate.getSupportActionBar();
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+        LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
+        // Remove the incorrect lint warning below that the action bar might be null.
+        assert actionBar != null;
+
+        // Apply the proxy.
+        applyProxy(false);
+
+        // Set Do Not Track status.
+        if (doNotTrackEnabled) {
+            customHeaders.put("DNT", "1");
+        } else {
+            customHeaders.remove("DNT");
+        }
+
+        // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
+        CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+        AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
+        AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
+        AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
+
+        // Add the scrolling behavior to the layout parameters.
+        if (scrollAppBar) {
+            // Enable scrolling of the app bar.
+            swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+            toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+            findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+            tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+        } else {
+            // Disable scrolling of the app bar.
+            swipeRefreshLayoutParams.setBehavior(null);
+            toolbarLayoutParams.setScrollFlags(0);
+            findOnPageLayoutParams.setScrollFlags(0);
+            tabsLayoutParams.setScrollFlags(0);
+
+            // Expand the app bar if it is currently collapsed.
+            appBarLayout.setExpanded(true);
+        }
+
+        // Apply the modified layout parameters.
+        swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
+        toolbar.setLayoutParams(toolbarLayoutParams);
+        findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
+        tabsLinearLayout.setLayoutParams(tabsLayoutParams);
+
+        // Set the app bar scrolling for each WebView.
+        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+            // Get the WebView tab fragment.
+            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+
+            // Get the fragment view.
+            View fragmentView = webViewTabFragment.getView();
+
+            // Only modify the WebViews if they exist.
+            if (fragmentView != null) {
+                // Get the nested scroll WebView from the tab fragment.
+                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+
+                // Set the app bar scrolling.
+                nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
+            }
+        }
+
+        // Update the full screen browsing mode settings.
+        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+            // Update the visibility of the app bar, which might have changed in the settings.
+            if (hideAppBar) {
+                // Hide the tab linear layout.
+                tabsLinearLayout.setVisibility(View.GONE);
+
+                // Hide the action bar.
+                actionBar.hide();
+            } else {
+                // Show the tab linear layout.
+                tabsLinearLayout.setVisibility(View.VISIBLE);
+
+                // Show the action bar.
+                actionBar.show();
+            }
+
+            // Hide the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                AdHelper.hideAd(findViewById(R.id.adview));
+            }
+
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else {  // Privacy Browser is not in full screen browsing mode.
+            // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
+            inFullScreenBrowsingMode = false;
+
+            // Show the tab linear layout.
+            tabsLinearLayout.setVisibility(View.VISIBLE);
+
+            // Show the action bar.
+            actionBar.show();
+
+            // Show the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
+                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
+            }
+
+            // Remove the `SYSTEM_UI` flags from the root frame layout.
+            rootFrameLayout.setSystemUiVisibility(0);
+        }
+    }
+
     @Override
     public void navigateHistory(String url, int steps) {
         // Apply the domain settings.
@@ -3768,9 +3864,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the current page position.
                 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
 
-                // Get a handle for the tab layout.
-                TabLayout tabLayout = findViewById(R.id.tablayout);
-
                 // Get the corresponding tab.
                 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
 
@@ -3911,22 +4004,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
                 String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
 
-                // Create the pinned SSL date variables.
+                // Get the pinned SSL date longs.
+                long pinnedSslStartDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE));
+                long pinnedSslEndDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE));
+
+                // Define the pinned SSL date variables.
                 Date pinnedSslStartDate;
                 Date pinnedSslEndDate;
 
-                // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0.
-                if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
+                // Set the pinned SSL certificate start date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0.
+                if (pinnedSslStartDateLong == 0) {
                     pinnedSslStartDate = null;
                 } else {
-                    pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+                    pinnedSslStartDate = new Date(pinnedSslStartDateLong);
                 }
 
-                // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0.
-                if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
+                // Set the pinned SSL certificate end date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0.
+                if (pinnedSslEndDateLong == 0) {
                     pinnedSslEndDate = null;
                 } else {
-                    pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                    pinnedSslEndDate = new Date(pinnedSslEndDateLong);
                 }
 
                 // Close the current host domain settings cursor.
@@ -4056,12 +4153,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
                             // Set the WebView theme according to the current system theme status.
-                            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {  // The system is in night mode.
-                                // Turn on the WebView dark mode.
-                                WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
-                            } else {  // The system is in day mode.
+                            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
                                 // Turn off the WebView dark mode.
                                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+                            } else {  // The system is in night mode.
+                                // Turn on the WebView dark mode.
+                                WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                             }
                             break;
 
@@ -4110,11 +4207,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the current theme status.
                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
-                // Set a background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
+                // Set a background on the URL relative layout to indicate that custom domain settings are being used.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
                 } else {
-                    urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
+                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
                 }
             } else {  // The new URL does not have custom domain settings.  Load the defaults.
                 // Store the values from the shared preferences.
@@ -4211,12 +4308,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
                         // Set the WebView theme according to the current system theme status.
-                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {  // The system is in night mode.
-                            // Turn on the WebView dark mode.
-                            WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
-                        } else {  // The system is in day mode.
+                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
                             // Turn off the WebView dark mode.
                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+                        } else {  // The system is in night mode.
+                            // Turn on the WebView dark mode.
+                            WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                         }
                     }
                 }
@@ -4227,8 +4324,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Set the loading of webpage images.
                 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
 
-                // Set a transparent background on URL edit text.  The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21.
-                urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
+                // Set a transparent background on URL edit text.
+                urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
             }
 
             // Close the domains database helper.
@@ -4245,9 +4342,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void applyProxy(boolean reloadWebViews) {
-        // Get a handle for the app bar layout.
-        AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
-
         // Set the proxy according to the mode.  `this` refers to the current activity where an alert dialog might be displayed.
         ProxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode);
 
@@ -4275,10 +4369,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             case ProxyHelper.TOR:
                 // Set the app bar background to indicate proxying through Orbot is enabled.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    appBarLayout.setBackgroundResource(R.color.dark_blue_30);
-                } else {
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                     appBarLayout.setBackgroundResource(R.color.blue_50);
+                } else {
+                    appBarLayout.setBackgroundResource(R.color.dark_blue_30);
                 }
 
                 // Check to see if Orbot is installed.
@@ -4317,10 +4411,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             case ProxyHelper.I2P:
                 // Set the app bar background to indicate proxying through Orbot is enabled.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    appBarLayout.setBackgroundResource(R.color.dark_blue_30);
-                } else {
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                     appBarLayout.setBackgroundResource(R.color.blue_50);
+                } else {
+                    appBarLayout.setBackgroundResource(R.color.dark_blue_30);
                 }
 
                 // Check to see if I2P is installed.
@@ -4344,10 +4438,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             case ProxyHelper.CUSTOM:
                 // Set the app bar background to indicate proxying through Orbot is enabled.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    appBarLayout.setBackgroundResource(R.color.dark_blue_30);
-                } else {
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                     appBarLayout.setBackgroundResource(R.color.blue_50);
+                } else {
+                    appBarLayout.setBackgroundResource(R.color.dark_blue_30);
                 }
                 break;
         }
@@ -4399,10 +4493,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             if (currentWebView.getAcceptFirstPartyCookies()) {  // First-party cookies are enabled.
                 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
             } else {  // First-party cookies are disabled.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
-                } else {
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                     firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
+                } else {
+                    firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
                 }
             }
 
@@ -4410,24 +4504,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             if (currentWebView.getSettings().getJavaScriptEnabled() && currentWebView.getSettings().getDomStorageEnabled()) {  // Both JavaScript and DOM storage are enabled.
                 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
             } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is enabled but DOM storage is disabled.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_night);
-                } else {
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                     domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_day);
+                } else {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_night);
                 }
             } else {  // JavaScript is disabled, so DOM storage is ghosted.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_night);
-                } else {
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                     domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_day);
+                } else {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_night);
                 }
             }
 
             // Update the refresh icon.
-            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                refreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
-            } else {
+            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                 refreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
+            } else {
+                refreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
             }
 
             // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar.
@@ -4653,8 +4747,63 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         ultraList = combinedBlocklists.get(4);
         ultraPrivacy = combinedBlocklists.get(5);
 
-        // Add the first tab.
-        addNewTab("", true);
+        // Check to see if the activity has been restarted.
+        if ((savedStateArrayList == null) || (savedStateArrayList.size() == 0)) {  // The activity has not been restarted or it was restarted on start to force the night theme.
+            // Add the first tab.
+            addNewTab("", true);
+        } else {  // The activity has been restarted.
+            // Restore each tab.  Once the minimum API >= 24, a `forEach()` command can be used.
+            for (int i = 0; i < savedStateArrayList.size(); i++) {
+                // Add a new tab.
+                tabLayout.addTab(tabLayout.newTab());
+
+                // Get the new tab.
+                TabLayout.Tab newTab = tabLayout.getTabAt(i);
+
+                // Remove the lint warning below that the current tab might be null.
+                assert newTab != null;
+
+                // Set a custom view on the new tab.
+                newTab.setCustomView(R.layout.tab_custom_view);
+
+                // Add the new page.
+                webViewPagerAdapter.restorePage(savedStateArrayList.get(i), savedNestedScrollWebViewStateArrayList.get(i));
+            }
+
+            // Reset the saved state variables.
+            savedStateArrayList = null;
+            savedNestedScrollWebViewStateArrayList = null;
+
+            // Restore the selected tab position.
+            if (savedTabPosition == 0) {  // The first tab is selected.
+                // Set the first page as the current WebView.
+                setCurrentWebView(0);
+            } else {  // the first tab is not selected.
+                // Move to the selected tab.
+                webViewPager.setCurrentItem(savedTabPosition);
+            }
+
+            // Process the new intent if it exists.
+            if (newIntentUrl != null) {
+                // Get the shared preferences.
+                SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+                // Add a new tab if specified in the preferences.
+                if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
+                    // Set the loading new intent flag.
+                    loadingNewIntent = true;
+
+                    // Add a new tab.
+                    addNewTab(newIntentUrl, true);
+                } else {  // Load the URL in the current tab.
+                    // Make it so.
+                    loadUrl(currentWebView, newIntentUrl);
+                }
+
+                // Reset the new intent URL.
+                newIntentUrl = null;
+            }
+        }
     }
 
     public void addTab(View view) {
@@ -4663,13 +4812,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void addNewTab(String url, boolean moveToTab) {
-        // Sanitize the URL.
-        url = sanitizeUrl(url);
-
-        // Get a handle for the tab layout and the view pager.
-        TabLayout tabLayout = findViewById(R.id.tablayout);
-        ViewPager webViewPager = findViewById(R.id.webviewpager);
-
         // Get the new page number.  The page numbers are 0 indexed, so the new page number will match the current count.
         int newTabNumber = tabLayout.getTabCount();
 
@@ -4690,9 +4832,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     public void closeTab(View view) {
-        // Get a handle for the tab layout.
-        TabLayout tabLayout = findViewById(R.id.tablayout);
-
         // Run the command according to the number of tabs.
         if (tabLayout.getTabCount() > 1) {  // There is more than one tab open.
             // Close the current tab.
@@ -4703,11 +4842,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void closeCurrentTab() {
-        // Get handles for the views.
-        AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
-        TabLayout tabLayout = findViewById(R.id.tablayout);
-        ViewPager webViewPager = findViewById(R.id.webviewpager);
-
         // Get the current tab number.
         int currentTabNumber = tabLayout.getSelectedTabPosition();
 
@@ -4981,16 +5115,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the current theme status.
                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
-                // Set a green background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
+                // Set a green background on the URL relative layout to indicate that custom domain settings are being used.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
                 } else {
-                    urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
+                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
                 }
             } else {
-                urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
+                urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
             }
-        } else {  // The fragment has not been populated.  Try again in 100 milliseconds.
+        } else {  // The fragment has not been populated.  Try again in 100 milliseconds.  //TODO try to replace this with a post command.
             // Create a handler to set the current WebView.
             Handler setCurrentWebViewHandler = new Handler();
 
@@ -5006,7 +5140,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
-    public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url) {
+    public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState) {
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
@@ -5022,6 +5156,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
                 // Turn off the WebView dark mode.
                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+
+                // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
+                // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
+                nestedScrollWebView.setVisibility(View.VISIBLE);
             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
                 // Turn on the WebView dark mode.
                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
@@ -5030,12 +5168,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
                 // Set the WebView theme according to the current system theme status.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {  // The system is in night mode.
-                    // Turn on the WebView dark mode.
-                    WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
-                } else {  // The system is in day mode.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
                     // Turn off the WebView dark mode.
                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+
+                    // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
+                    // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
+                    nestedScrollWebView.setVisibility(View.VISIBLE);
+                } else {  // The system is in night mode.
+                    // Turn on the WebView dark mode.
+                    WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                 }
             }
         }
@@ -5050,7 +5192,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         ActionBar actionBar = appCompatDelegate.getSupportActionBar();
         LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
         EditText urlEditText = findViewById(R.id.url_edittext);
-        TabLayout tabLayout = findViewById(R.id.tablayout);
         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
         // Remove the incorrect lint warning below that the action bar might be null.
@@ -5302,12 +5443,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     //Stop the swipe to refresh indicator if it is running
                     swipeRefreshLayout.setRefreshing(false);
-                }
 
-                // If this is a new tab, the current WebView would have been created invisible in `webview_framelayout` to prevent a white background splash in night mode.
-                if (progress >= 50) {
-                    // Make the current WebView visible.
-                    currentWebView.setVisibility(View.VISIBLE);
+                    // Make the current WebView visible.  If this is a new tab, the current WebView would have been created invisible in `webview_framelayout` to prevent a white background splash in night mode.
+                    nestedScrollWebView.setVisibility(View.VISIBLE);
                 }
             }
 
@@ -5617,8 +5755,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get a handle for the navigation menu.
                 Menu navigationMenu = navigationView.getMenu();
 
-                // Get a handle for the navigation requests menu item.  The menu is 0 based.
-                MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
+                // Get a handle for the navigation requests menu item.
+                MenuItem navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
 
                 // Create an empty web resource response to be used if the resource request is blocked.
                 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
@@ -5960,9 +6098,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the preferences.
                 boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
 
-                // Get a handler for the app bar layout.
-                AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
-
                 // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.  This can't be done in `appAppSettings()` because the app bar is not yet populated there.
                 if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
                     // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
@@ -6031,10 +6166,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
                         // Set the stop icon according to the theme.
-                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                            refreshMenuItem.setIcon(R.drawable.close_night);
-                        } else {
+                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                             refreshMenuItem.setIcon(R.drawable.close_day);
+                        } else {
+                            refreshMenuItem.setIcon(R.drawable.close_night);
                         }
                     }
                 }
@@ -6064,10 +6199,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
                         // Set the icon according to the theme.
-                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
-                        } else {
+                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                             refreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
+                        } else {
+                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
                         }
                     }
                 }
@@ -6222,14 +6357,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
-        // Check to see if this is the first page.
-        if (pageNumber == 0) {
+        // Check to see if the state is being restored.
+        if (restoringState) {  // The state is being restored.
+            // Resume the nested scroll WebView JavaScript timers.
+            nestedScrollWebView.resumeTimers();
+        } else if (pageNumber == 0) {  // The first page is being loaded.
             // Set this nested scroll WebView as the current WebView.
             currentWebView = nestedScrollWebView;
 
-            // Apply the app settings from the shared preferences.
-            applyAppSettings();
-
             // Initialize the URL to load string.
             String urlToLoadString;
 
@@ -6257,6 +6392,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             } else if (launchingIntentUriData != null){  // The intent contains a URL.
                 // Store the URL.
                 urlToLoadString = launchingIntentUriData.toString();
+            } else if (!url.equals("")) {  // The activity has been restarted.
+                // Load the saved URL.
+                urlToLoadString = url;
             } else {  // The is no URL in the intent.
                 // Store the homepage to be loaded.
                 urlToLoadString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
@@ -6269,11 +6407,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 loadUrl(nestedScrollWebView, urlToLoadString);
             }
         } else {  // This is not the first tab.
-            // Apply the domain settings.
-            applyDomainSettings(nestedScrollWebView, url, false, false);
-
             // Load the URL.
-            nestedScrollWebView.loadUrl(url, customHeaders);
+            loadUrl(nestedScrollWebView, url);
 
             // Set the focus and display the keyboard if the URL is blank.
             if (url.equals("")) {
index 2adeb318a54a7b8410d40a17aa24395969f9dcfe..eb6fb4fe3b7be225a69bd37ed877780c23257252 100644 (file)
@@ -35,9 +35,10 @@ import android.widget.ResourceCursorAdapter;
 import android.widget.Spinner;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;  // The AndroidX toolbar must be used until the minimum API >= 21.
+import androidx.appcompat.widget.Toolbar;
 import androidx.fragment.app.DialogFragment;
 
 import com.stoutner.privacybrowser.R;
@@ -52,7 +53,10 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
     // The resource requests are populated by `MainWebViewActivity` before `RequestsActivity` is launched.
     public static List<String[]> resourceRequests;
 
-    // The list view is used in `onCreate()` and `launchViewRequestDialog()`.
+    // Initialize the class constants.
+    private final String LISTVIEW_POSITION = "listview_position";
+
+    // Define the class views.
     private ListView requestsListView;
 
     @Override
@@ -235,6 +239,24 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi
             // Display the view request dialog.  The list view is 0 based, so the position must be incremented by 1.
             launchViewRequestDialog(position + 1);
         });
+
+        // Check to see if the activity has been restarted.
+        if (savedInstanceState != null) {
+            // Scroll to the saved position.
+            requestsListView.post(() -> requestsListView.setSelection(savedInstanceState.getInt(LISTVIEW_POSITION)));
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Get the listview position.
+        int listViewPosition = requestsListView.getFirstVisiblePosition();
+
+        // Store the listview position in the bundle.
+        savedInstanceState.putInt(LISTVIEW_POSITION, listViewPosition);
     }
 
     @Override
index 51036fbc38bc82cf46b6d87fd97c070b275a6d90..e4899ea329669f741c3b6355558b7e3f4bceba6e 100644 (file)
@@ -24,7 +24,9 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.LocaleList;
 import android.preference.PreferenceManager;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
@@ -36,18 +38,27 @@ import android.view.View;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;  // The AndroidX toolbar must be used until the minimum API is >= 21.
+import androidx.appcompat.widget.Toolbar;
 import androidx.core.app.NavUtils;
 import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.ViewModelProvider;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 
+import com.google.android.material.snackbar.Snackbar;
 import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.asynctasks.GetSource;
 import com.stoutner.privacybrowser.dialogs.AboutViewSourceDialog;
+import com.stoutner.privacybrowser.helpers.ProxyHelper;
+import com.stoutner.privacybrowser.viewmodelfactories.WebViewSourceFactory;
+import com.stoutner.privacybrowser.viewmodels.WebViewSource;
+
+import java.net.Proxy;
+import java.util.Locale;
 
 public class ViewSourceActivity extends AppCompatActivity {
     // `activity` is used in `onCreate()` and `goBack()`.
@@ -84,14 +95,19 @@ public class ViewSourceActivity extends AppCompatActivity {
         String userAgent = intent.getStringExtra("user_agent");
         String currentUrl = intent.getStringExtra("current_url");
 
+        // Remove the incorrect lint warning below that the user agent might be null.
+        assert userAgent != null;
+
         // Store a handle for the current activity.
         activity = this;
 
         // Set the content view.
         setContentView(R.layout.view_source_coordinatorlayout);
 
-        // The AndroidX toolbar must be used until the minimum API is >= 21.
+        // Get a handle for the toolbar.
         Toolbar toolbar = findViewById(R.id.view_source_toolbar);
+
+        // Set the support action bar.
         setSupportActionBar(toolbar);
 
         // Get a handle for the action bar.
@@ -104,17 +120,32 @@ public class ViewSourceActivity extends AppCompatActivity {
         actionBar.setCustomView(R.layout.view_source_app_bar);
         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
-        // Get a handle for the url text box.
+        // Get handles for the views.
         EditText urlEditText = findViewById(R.id.url_edittext);
+        TextView requestHeadersTextView = findViewById(R.id.request_headers);
+        TextView responseMessageTextView = findViewById(R.id.response_message);
+        TextView responseHeadersTextView = findViewById(R.id.response_headers);
+        TextView responseBodyTextView = findViewById(R.id.response_body);
+        ProgressBar progressBar = findViewById(R.id.progress_bar);
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.view_source_swiperefreshlayout);
 
         // Populate the URL text box.
         urlEditText.setText(currentUrl);
 
-        // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
-        redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+        // Initialize the gray foreground color spans for highlighting the URLs.  The deprecated `getResources()` must be used until API >= 23.
         initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
         finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
 
+        // Get the current theme status.
+        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+        // Set the red color span according to the theme.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+        } else {
+            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
+        }
+
         // Apply text highlighting to the URL.
         highlightUrlText();
 
@@ -143,75 +174,175 @@ public class ViewSourceActivity extends AppCompatActivity {
             }
         });
 
-        // Set the go button on the keyboard to request new source data.
-        urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
-            // Request new source data if the enter key was pressed.
-            if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
-                // Hide the soft keyboard.
-                inputMethodManager.hideSoftInputFromWindow(urlEditText.getWindowToken(), 0);
+        // Set the refresh color scheme according to the theme.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
+        } else {
+            swipeRefreshLayout.setColorSchemeResources(R.color.violet_500);
+        }
 
-                // Remove the focus from the URL box.
-                urlEditText.clearFocus();
+        // Initialize a color background typed value.
+        TypedValue colorBackgroundTypedValue = new TypedValue();
 
-                // Get the URL.
-                String url = urlEditText.getText().toString();
+        // Get the color background from the theme.
+        getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
+
+        // Get the color background int from the typed value.
+        int colorBackgroundInt = colorBackgroundTypedValue.data;
+
+        // Set the swipe refresh background color.
+        swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
 
-                // Get new source data for the current URL if it beings with `http`.
-                if (url.startsWith("http")) {
-                    new GetSource(this, this, userAgent).execute(url);
+        // Get the Do Not Track status.
+        boolean doNotTrack = sharedPreferences.getBoolean("do_not_track", false);
+
+        // Instantiate a locale string.
+        String localeString;
+
+        // Populate the locale string.
+        if (Build.VERSION.SDK_INT >= 24) {  // SDK >= 24 has a list of locales.
+            // Get the list of locales.
+            LocaleList localeList = getResources().getConfiguration().getLocales();
+
+            // Initialize a string builder to extract the locales from the list.
+            StringBuilder localesStringBuilder = new StringBuilder();
+
+            // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
+            int q = 10;
+
+            // Populate the string builder with the contents of the locales list.
+            for (int i = 0; i < localeList.size(); i++) {
+                // Append a comma if there is already an item in the string builder.
+                if (i > 0) {
+                    localesStringBuilder.append(",");
                 }
 
-                // Consume the key press.
-                return true;
-            } else {
-                // Do not consume the key press.
-                return false;
+                // Get the locale from the list.
+                Locale locale = localeList.get(i);
+
+                // Add the locale to the string.  `locale` by default displays as `en_US`, but WebView uses the `en-US` format.
+                localesStringBuilder.append(locale.getLanguage());
+                localesStringBuilder.append("-");
+                localesStringBuilder.append(locale.getCountry());
+
+                // If not the first locale, append `;q=0.x`, which drops by .1 for each removal from the main locale until q=0.1.
+                if (q < 10) {
+                    localesStringBuilder.append(";q=0.");
+                    localesStringBuilder.append(q);
+                }
+
+                // Decrement `q` if it is greater than 1.
+                if (q > 1) {
+                    q--;
+                }
+
+                // Add a second entry for the language only portion of the locale.
+                localesStringBuilder.append(",");
+                localesStringBuilder.append(locale.getLanguage());
+
+                // Append `1;q=0.x`, which drops by .1 for each removal form the main locale until q=0.1.
+                localesStringBuilder.append(";q=0.");
+                localesStringBuilder.append(q);
+
+                // Decrement `q` if it is greater than 1.
+                if (q > 1) {
+                    q--;
+                }
             }
+
+            // Store the populated string builder in the locale string.
+            localeString = localesStringBuilder.toString();
+        } else {  // SDK < 24 only has a primary locale.
+            // Store the locale in the locale string.
+            localeString = Locale.getDefault().toString();
+        }
+
+        // Instantiate the proxy helper.
+        ProxyHelper proxyHelper = new ProxyHelper();
+
+        // Get the current proxy.
+        Proxy proxy = proxyHelper.getCurrentProxy(this);
+
+        // Make the progress bar visible.
+        progressBar.setVisibility(View.VISIBLE);
+
+        // Set the progress bar to be indeterminate.
+        progressBar.setIndeterminate(true);
+
+        // Instantiate the WebView source factory.
+        ViewModelProvider.Factory webViewSourceFactory = new WebViewSourceFactory(currentUrl, userAgent, doNotTrack, localeString, proxy, MainWebViewActivity.executorService);
+
+        // Instantiate the WebView source view model class.
+        final WebViewSource webViewSource = new ViewModelProvider(this, webViewSourceFactory).get(WebViewSource.class);
+
+        // Create a source observer.
+        webViewSource.observeSource().observe(this, sourceStringArray -> {
+            // Populate the text views.  This can take a long time, and freezes the user interface, if the response body is particularly large.
+            requestHeadersTextView.setText(sourceStringArray[0]);
+            responseMessageTextView.setText(sourceStringArray[1]);
+            responseHeadersTextView.setText(sourceStringArray[2]);
+            responseBodyTextView.setText(sourceStringArray[3]);
+
+            // Hide the progress bar.
+            progressBar.setIndeterminate(false);
+            progressBar.setVisibility(View.GONE);
+
+            //Stop the swipe to refresh indicator if it is running
+            swipeRefreshLayout.setRefreshing(false);
         });
 
-        // Get a handle for the swipe refresh layout.
-        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.view_source_swiperefreshlayout);
+        // Create an error observer.
+        webViewSource.observeErrors().observe(this, errorString -> {
+            // Display an error snackbar if the string is not `""`.
+            if (!errorString.equals("")) {
+                Snackbar.make(swipeRefreshLayout, errorString, Snackbar.LENGTH_LONG).show();
+            }
+        });
 
         // Implement swipe to refresh.
         swipeRefreshLayout.setOnRefreshListener(() -> {
+            // Make the progress bar visible.
+            progressBar.setVisibility(View.VISIBLE);
+
+            // Set the progress bar to be indeterminate.
+            progressBar.setIndeterminate(true);
+
             // Get the URL.
-            String url = urlEditText.getText().toString();
+            String urlString = urlEditText.getText().toString();
 
-            // Get new source data for the URL if it begins with `http`.
-            if (url.startsWith("http")) {
-                new GetSource(this, this, userAgent).execute(url);
-            } else {
-                // Stop the refresh animation.
-                swipeRefreshLayout.setRefreshing(false);
-            }
+            // Get the updated source.
+            webViewSource.updateSource(urlString);
         });
 
-        // Get the current theme status.
-        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        // Set the go button on the keyboard to request new source data.
+        urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
+            // Request new source data if the enter key was pressed.
+            if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+                // Hide the soft keyboard.
+                inputMethodManager.hideSoftInputFromWindow(urlEditText.getWindowToken(), 0);
 
-        // Set the refresh color scheme according to the theme.
-        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-            swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
-        } else {
-            swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
-        }
+                // Remove the focus from the URL box.
+                urlEditText.clearFocus();
 
-        // Initialize a color background typed value.
-        TypedValue colorBackgroundTypedValue = new TypedValue();
+                // Make the progress bar visible.
+                progressBar.setVisibility(View.VISIBLE);
 
-        // Get the color background from the theme.
-        getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
+                // Set the progress bar to be indeterminate.
+                progressBar.setIndeterminate(true);
 
-        // Get the color background int from the typed value.
-        int colorBackgroundInt = colorBackgroundTypedValue.data;
+                // Get the URL.
+                String urlString = urlEditText.getText().toString();
 
-        // Set the swipe refresh background color.
-        swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
+                // Get the updated source.
+                webViewSource.updateSource(urlString);
 
-        // Get the source using an AsyncTask if the URL begins with `http`.
-        if ((currentUrl != null) && currentUrl.startsWith("http")) {
-            new GetSource(this, this, userAgent).execute(currentUrl);
-        }
+                // Consume the key press.
+                return true;
+            } else {
+                // Do not consume the key press.
+                return false;
+            }
+        });
     }
 
     @Override
index d1583c2f03eae490bba4e47a32abc7a69b0f572f..9d4c9191d3a745d880a9cf89506660110615ae55 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -30,7 +30,7 @@ import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.fragments.AboutTabFragment;
 
 public class AboutPagerAdapter extends FragmentPagerAdapter {
-    // Define the class variable to store the blocklist versions.
+    // Define the class variables.
     private Context context;
     private String[] blocklistVersions;
 
@@ -38,10 +38,8 @@ public class AboutPagerAdapter extends FragmentPagerAdapter {
         // Run the default commands.
         super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
 
-        // Store the context in a class variable.
+        // Store the class variables.
         this.context = context;
-
-        // Store the blocklist versions in a class variable.
         this.blocklistVersions = blocklistVersions;
     }
 
index fccf188316fc88efae4251b09cfae69a88440cf4..26d376c2b5cea915ab58faac48416385dc516b03 100644 (file)
@@ -30,14 +30,15 @@ import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.fragments.GuideTabFragment;
 
 public class GuidePagerAdapter extends FragmentPagerAdapter {
-    // Define the class context variable.
+    // Define the class variables.
     private Context context;
 
+    // The default constructor.
     public GuidePagerAdapter(FragmentManager fragmentManager, Context context) {
         // Run the default commands.
         super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
 
-        // Store the context in a class variable.
+        // Store the class variables.
         this.context = context;
     }
 
index 535512a5ebb4fd40cc427f2cdfdafa43816438a0..510ac7396a1a63a6d813061652274ca696eb706a 100644 (file)
@@ -19,6 +19,8 @@
 
 package com.stoutner.privacybrowser.adapters;
 
+import android.os.Bundle;
+
 import androidx.annotation.NonNull;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
@@ -34,9 +36,9 @@ public class WebViewPagerAdapter extends FragmentPagerAdapter {
     private LinkedList<WebViewTabFragment> webViewFragmentsList = new LinkedList<>();
 
     // Define the constructor.
-    public WebViewPagerAdapter(FragmentManager fragmentManager){
+    public WebViewPagerAdapter(FragmentManager fragmentManager) {
         // Run the default commands.
-        super(fragmentManager);
+        super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
     }
 
     @Override
@@ -101,19 +103,27 @@ public class WebViewPagerAdapter extends FragmentPagerAdapter {
         return position;
     }
 
-    public void addPage(int pageNumber, ViewPager webViewPager, String url, boolean moveToTab) {
+    public void addPage(int pageNumber, ViewPager webViewPager, String url, boolean moveToNewPage) {
         // Add a new page.
         webViewFragmentsList.add(WebViewTabFragment.createPage(pageNumber, url));
 
         // Update the view pager.
         notifyDataSetChanged();
 
-        // Move to the new page if it isn't the first one.
-        if (pageNumber > 0 && moveToTab) {
+        // Move to the new page if indicated.
+        if (moveToNewPage) {
             webViewPager.setCurrentItem(pageNumber);
         }
     }
 
+    public void restorePage(Bundle savedState, Bundle savedNestedScrollWebViewState) {
+        // Restore the page.
+        webViewFragmentsList.add(WebViewTabFragment.restorePage(savedState, savedNestedScrollWebViewState));
+
+        // Update the view pager.
+        notifyDataSetChanged();
+    }
+
     public boolean deletePage(int pageNumber, ViewPager webViewPager) {
         // Delete the page.
         webViewFragmentsList.remove(pageNumber);
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetLogcat.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetLogcat.java
new file mode 100644 (file)
index 0000000..4722dbf
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.asynctasks;
+
+import android.app.Activity;
+import android.os.AsyncTask;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import com.stoutner.privacybrowser.R;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.ref.WeakReference;
+
+// `Void` does not declare any parameters.  `Void` does not declare progress units.  `String` contains the results.
+public class GetLogcat extends AsyncTask<Void, Void, String> {
+    // Define the class variables.
+    private final WeakReference<Activity> activityWeakReference;
+    private int scrollViewYPositionInt;
+
+    // The public constructor.
+    public GetLogcat(Activity activity, int scrollViewYPositionInt) {
+        // Populate the weak reference to the calling activity.
+        activityWeakReference = new WeakReference<>(activity);
+
+        // Store the scrollview Y position.
+        this.scrollViewYPositionInt = scrollViewYPositionInt;
+    }
+
+    @Override
+    protected String doInBackground(Void... parameters) {
+        // Get a handle for the activity.
+        Activity activity = activityWeakReference.get();
+
+        // Abort if the activity is gone.
+        if ((activity == null) || activity.isFinishing()) {
+            return "";
+        }
+
+        // Create a log string builder.
+        StringBuilder logStringBuilder = new StringBuilder();
+
+        try {
+            // Get the logcat.  `-b all` gets all the buffers (instead of just crash, main, and system).  `-v long` produces more complete information.  `-d` dumps the logcat and exits.
+            Process process = Runtime.getRuntime().exec("logcat -b all -v long -d");
+
+            // Wrap the logcat in a buffered reader.
+            BufferedReader logBufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+
+            // Create a log transfer string.
+            String logTransferString;
+
+            // Use the log transfer string to copy the logcat from the buffered reader to the string builder.
+            while ((logTransferString = logBufferedReader.readLine()) != null) {
+                // Append a line.
+                logStringBuilder.append(logTransferString);
+
+                // Append a line break.
+                logStringBuilder.append("\n");
+            }
+
+            // Close the buffered reader.
+            logBufferedReader.close();
+        } catch (IOException exception) {
+            // Do nothing.
+        }
+
+        // Return the logcat.
+        return logStringBuilder.toString();
+    }
+
+    // `onPostExecute()` operates on the UI thread.
+    @Override
+    protected void onPostExecute(String logcatString) {
+        // Get a handle for the activity.
+        Activity activity = activityWeakReference.get();
+
+        // Abort if the activity is gone.
+        if ((activity == null) || activity.isFinishing()) {
+            return;
+        }
+
+        // Get handles for the views.
+        TextView logcatTextView = activity.findViewById(R.id.logcat_textview);
+        SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.logcat_swiperefreshlayout);
+        ScrollView scrollView = activity.findViewById(R.id.logcat_scrollview);
+
+        // Display the logcat.
+        logcatTextView.setText(logcatString);
+
+        // Update the scroll position after the text is populated.
+        logcatTextView.post(() -> {
+            // Set the scroll position.
+            scrollView.setScrollY(scrollViewYPositionInt);
+        });
+
+        // Stop the swipe to refresh animation if it is displayed.
+        swipeRefreshLayout.setRefreshing(false);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java
deleted file mode 100644 (file)
index f5b4e54..0000000
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.Typeface;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.LocaleList;
-import android.preference.PreferenceManager;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.StyleSpan;
-import android.view.View;
-import android.webkit.CookieManager;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.helpers.ProxyHelper;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.HttpURLConnection;
-import java.net.Proxy;
-import java.net.URL;
-import java.util.Locale;
-
-// This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `SpannableStringBuilder[]` contains the results.
-public class GetSource extends AsyncTask<String, Void, SpannableStringBuilder[]> {
-    // Define weak references to the calling context and activity.
-    private WeakReference<Context> contextWeakReference;
-    private WeakReference<Activity> activityWeakReference;
-
-    // Store the user agent.
-    private String userAgent;
-
-    public GetSource(Context context, Activity activity, String userAgent) {
-        // Populate the weak references to the calling context and activity.
-        contextWeakReference = new WeakReference<>(context);
-        activityWeakReference = new WeakReference<>(activity);
-
-        // Store the user agent.
-        this.userAgent = userAgent;
-    }
-
-    // `onPreExecute()` operates on the UI thread.
-    @Override
-    protected void onPreExecute() {
-        // Get a handle for the calling activity.
-        Activity activity = activityWeakReference.get();
-
-        // Abort if the activity is gone.
-        if ((activity == null) || activity.isFinishing()) {
-            return;
-        }
-
-        // Get a handle for the progress bar.
-        ProgressBar progressBar = activity.findViewById(R.id.progress_bar);
-
-        // Make the progress bar visible.
-        progressBar.setVisibility(View.VISIBLE);
-
-        // Set the progress bar to be indeterminate.
-        progressBar.setIndeterminate(true);
-    }
-
-    @Override
-    protected SpannableStringBuilder[] doInBackground(String... formattedUrlString) {
-        // Initialize the response body String.
-        SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
-        SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
-        SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
-        SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
-
-        // Get a handle for the context and activity.
-        Context context = contextWeakReference.get();
-        Activity activity = activityWeakReference.get();
-
-        // Abort if the activity is gone.
-        if ((activity == null) || activity.isFinishing()) {
-            return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
-        }
-
-        // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
-        try {
-            // Get the current URL from the main activity.
-            URL url = new URL(formattedUrlString[0]);
-
-            // Instantiate the proxy helper.
-            ProxyHelper proxyHelper = new ProxyHelper();
-
-            // Get the current proxy.
-            Proxy proxy = proxyHelper.getCurrentProxy(context);
-
-            // Open a connection to the URL.  No data is actually sent at this point.
-            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
-
-            // Define the variables necessary to build the request headers.
-            requestHeadersBuilder = new SpannableStringBuilder();
-            int oldRequestHeadersBuilderLength;
-            int newRequestHeadersBuilderLength;
-
-
-            // Set the `Host` header property.
-            httpUrlConnection.setRequestProperty("Host", url.getHost());
-
-            // Add the `Host` header to the string builder and format the text.
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Host");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ");
-            requestHeadersBuilder.append(url.getHost());
-
-
-            // Set the `Connection` header property.
-            httpUrlConnection.setRequestProperty("Connection", "keep-alive");
-
-            // Add the `Connection` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Connection");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  keep-alive");
-
-
-            // Set the `Upgrade-Insecure-Requests` header property.
-            httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
-
-            // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Upgrade-Insecure_Requests");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  1");
-
-
-            // Set the `User-Agent` header property.
-            httpUrlConnection.setRequestProperty("User-Agent", userAgent);
-
-            // Add the `User-Agent` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("User-Agent");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ");
-            requestHeadersBuilder.append(userAgent);
-
-
-            // Set the `x-requested-with` header property.
-            httpUrlConnection.setRequestProperty("x-requested-with", "");
-
-            // Add the `x-requested-with` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("x-requested-with");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ");
-
-
-            // Set the `Sec-Fetch-Site` header property.
-            httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
-
-            // Add the `Sec-Fetch-Site` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Sec-Fetch-Site");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  none");
-
-
-            // Set the `Sec-Fetch-Mode` header property.
-            httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
-
-            // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Sec-Fetch-Mode");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  navigate");
-
-
-            // Set the `Sec-Fetch-User` header property.
-            httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
-
-            // Add the `Sec-Fetch-User` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Sec-Fetch-User");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ?1");
-
-
-            // Get a handle for the shared preferences.
-            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
-
-            // Only populate `Do Not Track` if it is enabled.
-            if (sharedPreferences.getBoolean("do_not_track", false)) {
-                // Set the `dnt` header property.
-                httpUrlConnection.setRequestProperty("dnt", "1");
-
-                // Add the `dnt` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("dnt");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  1");
-            }
-
-
-            // Set the `Accept` header property.
-            httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
-
-            // Add the `Accept` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Accept");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ");
-            requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
-
-
-            // Instantiate a locale string.
-            String localeString;
-
-            // Populate the locale string.
-            if (Build.VERSION.SDK_INT >= 24) {  // SDK >= 24 has a list of locales.
-                // Get the list of locales.
-                LocaleList localeList = activity.getResources().getConfiguration().getLocales();
-
-                // Initialize a string builder to extract the locales from the list.
-                StringBuilder localesStringBuilder = new StringBuilder();
-
-                // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
-                int q = 10;
-
-                // Populate the string builder with the contents of the locales list.
-                for (int i = 0; i < localeList.size(); i++) {
-                    // Append a comma if there is already an item in the string builder.
-                    if (i > 0) {
-                        localesStringBuilder.append(",");
-                    }
-
-                    // Get the locale from the list.
-                    Locale locale = localeList.get(i);
-
-                    // Add the locale to the string.  `locale` by default displays as `en_US`, but WebView uses the `en-US` format.
-                    localesStringBuilder.append(locale.getLanguage());
-                    localesStringBuilder.append("-");
-                    localesStringBuilder.append(locale.getCountry());
-
-                    // If not the first locale, append `;q=0.x`, which drops by .1 for each removal from the main locale until q=0.1.
-                    if (q < 10) {
-                        localesStringBuilder.append(";q=0.");
-                        localesStringBuilder.append(q);
-                    }
-
-                    // Decrement `q` if it is greater than 1.
-                    if (q > 1) {
-                        q--;
-                    }
-
-                    // Add a second entry for the language only portion of the locale.
-                    localesStringBuilder.append(",");
-                    localesStringBuilder.append(locale.getLanguage());
-
-                    // Append `1;q=0.x`, which drops by .1 for each removal form the main locale until q=0.1.
-                    localesStringBuilder.append(";q=0.");
-                    localesStringBuilder.append(q);
-
-                    // Decrement `q` if it is greater than 1.
-                    if (q > 1) {
-                        q--;
-                    }
-                }
-
-                // Store the populated string builder in the locale string.
-                localeString = localesStringBuilder.toString();
-            } else {  // SDK < 24 only has a primary locale.
-                // Store the locale in the locale string.
-                localeString = Locale.getDefault().toString();
-            }
-
-            // Set the `Accept-Language` header property.
-            httpUrlConnection.setRequestProperty("Accept-Language", localeString);
-
-            // Add the `Accept-Language` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Accept-Language");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ");
-            requestHeadersBuilder.append(localeString);
-
-
-            // Get the cookies for the current domain.
-            String cookiesString = CookieManager.getInstance().getCookie(url.toString());
-
-            // Only process the cookies if they are not null.
-            if (cookiesString != null) {
-                // Add the cookies to the header property.
-                httpUrlConnection.setRequestProperty("Cookie", cookiesString);
-
-                // Add the cookie header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("Cookie");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  ");
-                requestHeadersBuilder.append(cookiesString);
-            }
-
-
-            // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
-            // Add the `Accept-Encoding` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Accept-Encoding");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  gzip");
-
-
-            // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
-            try {
-                // Initialize the string builders.
-                responseMessageBuilder = new SpannableStringBuilder();
-                responseHeadersBuilder = new SpannableStringBuilder();
-
-                // Get the response code, which causes the connection to the server to be made.
-                int responseCode = httpUrlConnection.getResponseCode();
-
-                // Populate the response message string builder.
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    responseMessageBuilder.append(String.valueOf(responseCode));
-                    int newLength = responseMessageBuilder.length();
-                    responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                responseMessageBuilder.append(":  ");
-                responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
-
-                // Initialize the iteration variable.
-                int i = 0;
-
-                // Iterate through the received header fields.
-                while (httpUrlConnection.getHeaderField(i) != null) {
-                    // Add a new line if there is already information in the string builder.
-                    if (i > 0) {
-                        responseHeadersBuilder.append(System.getProperty("line.separator"));
-                    }
-
-                    // Add the header to the string builder and format the text.
-                    if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                        responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    } else {  // Older versions not so much.
-                        int oldLength = responseHeadersBuilder.length();
-                        responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
-                        int newLength = responseHeadersBuilder.length();
-                        responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
-                    responseHeadersBuilder.append(":  ");
-                    responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
-
-                    // Increment the iteration variable.
-                    i++;
-                }
-
-                // Instantiate an input stream for the response body.
-                InputStream inputStream;
-
-                // Get the correct input stream based on the response code.
-                if (responseCode == 404) {  // Get the error stream.
-                    inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
-                } else {  // Get the response body stream.
-                    inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
-                }
-
-                // Initialize the byte array output stream and the conversion buffer byte array.
-                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-                byte[] conversionBufferByteArray = new byte[1024];
-
-                // Define the buffer length variable.
-                int bufferLength;
-
-                try {
-                    // Attempt to read data from the input stream and store it in the conversion buffer byte array.  Also store the amount of data read in the buffer length variable.
-                    while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
-                        // Write the contents of the conversion buffer to the byte array output stream.
-                        byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
-                    }
-                } catch (IOException exception) {
-                    // Do nothing.
-                }
-
-                // Close the input stream.
-                inputStream.close();
-
-                // Populate the response body string with the contents of the byte array output stream.
-                responseBodyBuilder.append(byteArrayOutputStream.toString());
-            } finally {
-                // Disconnect HTTP URL connection.
-                httpUrlConnection.disconnect();
-            }
-        } catch (Exception exception) {
-            // Do nothing.
-        }
-
-        // Return the response body string as the result.
-        return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
-    }
-
-    // `onPostExecute()` operates on the UI thread.
-    @Override
-    protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){
-        // Get a handle for the activity.
-        Activity activity = activityWeakReference.get();
-
-        // Abort if the activity is gone.
-        if ((activity == null) || activity.isFinishing()) {
-            return;
-        }
-
-        // Get handles for the text views.
-        TextView requestHeadersTextView = activity.findViewById(R.id.request_headers);
-        TextView responseMessageTextView = activity.findViewById(R.id.response_message);
-        TextView responseHeadersTextView = activity.findViewById(R.id.response_headers);
-        TextView responseBodyTextView = activity.findViewById(R.id.response_body);
-        ProgressBar progressBar = activity.findViewById(R.id.progress_bar);
-        SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.view_source_swiperefreshlayout);
-
-        // Populate the text views.  This can take a long time, and freeze the user interface, if the response body is particularly large.
-        requestHeadersTextView.setText(viewSourceStringArray[0]);
-        responseMessageTextView.setText(viewSourceStringArray[1]);
-        responseHeadersTextView.setText(viewSourceStringArray[2]);
-        responseBodyTextView.setText(viewSourceStringArray[3]);
-
-        // Hide the progress bar.
-        progressBar.setIndeterminate(false);
-        progressBar.setVisibility(View.GONE);
-
-        //Stop the swipe to refresh indicator if it is running
-        swipeRefreshLayout.setRefreshing(false);
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetSourceBackgroundTask.java b/app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetSourceBackgroundTask.java
new file mode 100644 (file)
index 0000000..1311497
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.backgroundtasks;
+
+import android.graphics.Typeface;
+import android.os.Build;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.StyleSpan;
+import android.webkit.CookieManager;
+
+import com.stoutner.privacybrowser.viewmodels.WebViewSource;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+
+public class GetSourceBackgroundTask {
+    public SpannableStringBuilder[] acquire(String urlString, String userAgent, boolean doNotTrack, String localeString, Proxy proxy, WebViewSource webViewSource) {
+        // Initialize the spannable string builders.
+        SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
+        SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
+        SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
+        SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
+
+        // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
+        try {
+            // Get the current URL from the main activity.
+            URL url = new URL(urlString);
+
+            // Open a connection to the URL.  No data is actually sent at this point.
+            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
+
+            // Define the variables necessary to build the request headers.
+            requestHeadersBuilder = new SpannableStringBuilder();
+            int oldRequestHeadersBuilderLength;
+            int newRequestHeadersBuilderLength;
+
+
+            // Set the `Host` header property.
+            httpUrlConnection.setRequestProperty("Host", url.getHost());
+
+            // Add the `Host` header to the string builder and format the text.
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Host");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+            requestHeadersBuilder.append(url.getHost());
+
+
+            // Set the `Connection` header property.
+            httpUrlConnection.setRequestProperty("Connection", "keep-alive");
+
+            // Add the `Connection` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Connection");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  keep-alive");
+
+
+            // Set the `Upgrade-Insecure-Requests` header property.
+            httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
+
+            // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Upgrade-Insecure_Requests");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  1");
+
+
+            // Set the `User-Agent` header property.
+            httpUrlConnection.setRequestProperty("User-Agent", userAgent);
+
+            // Add the `User-Agent` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("User-Agent");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+            requestHeadersBuilder.append(userAgent);
+
+
+            // Set the `x-requested-with` header property.
+            httpUrlConnection.setRequestProperty("x-requested-with", "");
+
+            // Add the `x-requested-with` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("x-requested-with");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+
+
+            // Set the `Sec-Fetch-Site` header property.
+            httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
+
+            // Add the `Sec-Fetch-Site` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Sec-Fetch-Site");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  none");
+
+
+            // Set the `Sec-Fetch-Mode` header property.
+            httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
+
+            // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Sec-Fetch-Mode");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  navigate");
+
+
+            // Set the `Sec-Fetch-User` header property.
+            httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
+
+            // Add the `Sec-Fetch-User` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Sec-Fetch-User");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ?1");
+
+
+            // Only populate `Do Not Track` if it is enabled.
+            if (doNotTrack) {
+                // Set the `dnt` header property.
+                httpUrlConnection.setRequestProperty("dnt", "1");
+
+                // Add the `dnt` header to the string builder and format the text.
+                requestHeadersBuilder.append(System.getProperty("line.separator"));
+                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                    requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {  // Older versions not so much.
+                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.append("dnt");
+                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  1");
+            }
+
+
+            // Set the `Accept` header property.
+            httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
+
+            // Add the `Accept` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Accept");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+            requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
+
+
+            // Set the `Accept-Language` header property.
+            httpUrlConnection.setRequestProperty("Accept-Language", localeString);
+
+            // Add the `Accept-Language` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Accept-Language");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+            requestHeadersBuilder.append(localeString);
+
+
+            // Get the cookies for the current domain.
+            String cookiesString = CookieManager.getInstance().getCookie(url.toString());
+
+            // Only process the cookies if they are not null.
+            if (cookiesString != null) {
+                // Add the cookies to the header property.
+                httpUrlConnection.setRequestProperty("Cookie", cookiesString);
+
+                // Add the cookie header to the string builder and format the text.
+                requestHeadersBuilder.append(System.getProperty("line.separator"));
+                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                    requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {  // Older versions not so much.
+                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.append("Cookie");
+                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  ");
+                requestHeadersBuilder.append(cookiesString);
+            }
+
+
+            // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
+            // Add the `Accept-Encoding` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Accept-Encoding");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  gzip");
+
+
+            // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
+            try {
+                // Initialize the string builders.
+                responseMessageBuilder = new SpannableStringBuilder();
+                responseHeadersBuilder = new SpannableStringBuilder();
+
+                // Get the response code, which causes the connection to the server to be made.
+                int responseCode = httpUrlConnection.getResponseCode();
+
+                // Populate the response message string builder.
+                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                    responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {  // Older versions not so much.
+                    responseMessageBuilder.append(String.valueOf(responseCode));
+                    int newLength = responseMessageBuilder.length();
+                    responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                responseMessageBuilder.append(":  ");
+                responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
+
+                // Initialize the iteration variable.
+                int i = 0;
+
+                // Iterate through the received header fields.
+                while (httpUrlConnection.getHeaderField(i) != null) {
+                    // Add a new line if there is already information in the string builder.
+                    if (i > 0) {
+                        responseHeadersBuilder.append(System.getProperty("line.separator"));
+                    }
+
+                    // Add the header to the string builder and format the text.
+                    if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                        responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    } else {  // Older versions not so much.
+                        int oldLength = responseHeadersBuilder.length();
+                        responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
+                        int newLength = responseHeadersBuilder.length();
+                        responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                    responseHeadersBuilder.append(":  ");
+                    responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
+
+                    // Increment the iteration variable.
+                    i++;
+                }
+
+                // Instantiate an input stream for the response body.
+                InputStream inputStream;
+
+                // Get the correct input stream based on the response code.
+                if (responseCode == 404) {  // Get the error stream.
+                    inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
+                } else {  // Get the response body stream.
+                    inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
+                }
+
+                // Initialize the byte array output stream and the conversion buffer byte array.
+                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+                byte[] conversionBufferByteArray = new byte[1024];
+
+                // Define the buffer length variable.
+                int bufferLength;
+
+                try {
+                    // Attempt to read data from the input stream and store it in the conversion buffer byte array.  Also store the amount of data read in the buffer length variable.
+                    while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
+                        // Write the contents of the conversion buffer to the byte array output stream.
+                        byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
+                    }
+                } catch (IOException exception) {
+                    // Return the error message.
+                    webViewSource.returnError(exception.toString());
+                }
+
+                // Close the input stream.
+                inputStream.close();
+
+                // Populate the response body string with the contents of the byte array output stream.
+                responseBodyBuilder.append(byteArrayOutputStream.toString());
+            } finally {
+                // Disconnect HTTP URL connection.
+                httpUrlConnection.disconnect();
+            }
+        } catch (Exception exception) {
+            // Return the error message.
+            webViewSource.returnError(exception.toString());
+        }
+
+        // Return the response body string as the result.
+        return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
+    }
+}
index 4de3543173402cdd18c9d8fef528c161a9c3a610..a3c164521bdf22f98b72b80eab8b92f1a467c406 100644 (file)
@@ -205,7 +205,7 @@ class EditBookmarkDatabaseViewDialog: DialogFragment() {
         val matrixCursor = MatrixCursor(matrixCursorColumnNamesArray)
 
         // Add `Home Folder` as the first entry in the matrix folder.
-        matrixCursor.addRow(arrayOf<Any>(BookmarksDatabaseViewActivity.HOME_FOLDER_DATABASE_ID, getString(R.string.home_folder)))
+        matrixCursor.addRow(arrayOf(BookmarksDatabaseViewActivity.HOME_FOLDER_DATABASE_ID, getString(R.string.home_folder)))
 
         // Get a cursor with the list of all the folders.
         val foldersCursor = bookmarksDatabaseHelper.allFolders
@@ -321,15 +321,18 @@ class EditBookmarkDatabaseViewDialog: DialogFragment() {
             }
         })
 
-        // Update the edit button if the folder changes.
-        folderSpinner.onItemSelectedListener = object: OnItemSelectedListener {
-            override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
-                // Update the edit button.
-                updateEditButton(currentBookmarkName, currentUrl, currentFolderDatabaseId, currentDisplayOrder)
-            }
+        // Wait to set the on item selected listener until the spinner has been inflated.  Otherwise the dialog will crash on restart.
+        folderSpinner.post {
+            // Update the edit button if the folder changes.
+            folderSpinner.onItemSelectedListener = object : OnItemSelectedListener {
+                override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
+                    // Update the edit button.
+                    updateEditButton(currentBookmarkName, currentUrl, currentFolderDatabaseId, currentDisplayOrder)
+                }
 
-            override fun onNothingSelected(parent: AdapterView<*>?) {
-                // Do nothing.
+                override fun onNothingSelected(parent: AdapterView<*>?) {
+                    // Do nothing.
+                }
             }
         }
 
index 4b075d3b81924f220833598e31acaa8ac6dcf457..3a68a6169fc3d82394018be1f7038a38624df2e8 100644 (file)
@@ -264,12 +264,12 @@ public class SslCertificateErrorDialog extends DialogFragment {
         ForegroundColorSpan redColorSpan;
 
         // Set the color spans according to the theme.  The deprecated `getResources()` must be used until the minimum API >= 23.
-        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-            blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.violet_500));
-            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
-        } else {
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
             blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
             redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+        } else {
+            blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.violet_500));
+            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
         }
 
         // Setup the spans to display the certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
@@ -403,16 +403,14 @@ public class SslCertificateErrorDialog extends DialogFragment {
 
                 // Add each IP address to the string builder.
                 for (InetAddress inetAddress : inetAddressesArray) {
-                    if (ipAddresses.length() == 0) {  // This is the first IP address.
-                        // Add the IP Address to the string builder.
-                        ipAddresses.append(inetAddress.getHostAddress());
-                    } else {  // This is not the first IP address.
+                    // Check to see if this is not the first IP address.
+                    if (ipAddresses.length() > 0) {
                         // Add a line break to the string builder first.
                         ipAddresses.append("\n");
-
-                        // Add the IP address to the string builder.
-                        ipAddresses.append(inetAddress.getHostAddress());
                     }
+
+                    // Add the IP Address to the string builder.
+                    ipAddresses.append(inetAddress.getHostAddress());
                 }
             } catch (UnknownHostException exception) {
                 // Do nothing.
index 19b591c79ccbce41707037a8147e689a956bb610..c35b90b64b6231543d6aabc52afc116e6830054a 100644 (file)
@@ -54,9 +54,10 @@ import java.text.DateFormat;
 import java.util.Date;
 
 public class AboutTabFragment extends Fragment {
-    // Declare the class variables.
+    // Define the class variables.
     private int tabNumber;
     private String[] blocklistVersions;
+    private View tabLayout;
 
     public static AboutTabFragment createTab(int tabNumber, String[] blocklistVersions) {
         // Create a bundle.
@@ -94,9 +95,6 @@ public class AboutTabFragment extends Fragment {
 
     @Override
     public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
-        // Create a tab layout view.
-        View tabLayout;
-
         // Get a handle for the context and assert that it isn't null.
         Context context = getContext();
         assert context != null;
@@ -470,7 +468,27 @@ public class AboutTabFragment extends Fragment {
             }
         }
 
+        // Scroll the tab if the saved instance state is not null.
+        if (savedInstanceState != null) {
+            tabLayout.post(() -> {
+                tabLayout.setScrollX(savedInstanceState.getInt("scroll_x"));
+                tabLayout.setScrollY(savedInstanceState.getInt("scroll_y"));
+            });
+        }
+
         // Return the formatted `tabLayout`.
         return tabLayout;
     }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Save the scroll positions if the tab layout is not null, which can happen if a tab is not currently selected.
+        if (tabLayout != null) {
+            savedInstanceState.putInt("scroll_x", tabLayout.getScrollX());
+            savedInstanceState.putInt("scroll_y", tabLayout.getScrollY());
+        }
+    }
 }
\ No newline at end of file
index 1588ad3c2d5bedc6a31365486bcd68f411816e35..35d626935039f0c5a151df734ba148bf83d53c58 100644 (file)
@@ -44,12 +44,14 @@ import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RadioButton;
+import android.widget.ScrollView;
 import android.widget.Spinner;
-import android.widget.Switch;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.appcompat.widget.SwitchCompat;
 import androidx.cardview.widget.CardView;
+import androidx.core.content.res.ResourcesCompat;
 import androidx.fragment.app.Fragment;  // The AndroidX fragment must be used until minimum API >= 23.  Otherwise `getContext()` does not work.
 
 import com.stoutner.privacybrowser.R;
@@ -62,12 +64,16 @@ import java.util.Calendar;
 import java.util.Date;
 
 public class DomainSettingsFragment extends Fragment {
-    // `DATABASE_ID` is used by activities calling this fragment.
+    // Initialize the public class constants.  These are used by activities calling this fragment.
     public static final String DATABASE_ID = "database_id";
+    public static final String SCROLL_Y = "scroll_y";
 
-    // `databaseId` is public static so it can be accessed from `DomainsActivity`. It is also used in `onCreate()` and `onCreateView()`.
+    // Define the public variables.  `databaseId` is public static so it can be accessed from `DomainsActivity`. It is also used in `onCreate()` and `onCreateView()`.
     public static int databaseId;
 
+    // Define the class variables.
+    private int scrollY;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         // Run the default commands.
@@ -78,6 +84,7 @@ public class DomainSettingsFragment extends Fragment {
 
         // Store the database id in `databaseId`.
         databaseId = getArguments().getInt(DATABASE_ID);
+        scrollY = getArguments().getInt(SCROLL_Y);
     }
 
     // The deprecated `getDrawable()` must be used until the minimum API >= 21.
@@ -86,7 +93,7 @@ public class DomainSettingsFragment extends Fragment {
         // Inflate `domain_settings_fragment`.  `false` does not attach it to the root `container`.
         View domainSettingsView = inflater.inflate(R.layout.domain_settings_fragment, container, false);
 
-        // Get a handle for the context and the resources.
+        // Get handles for the context and the resources.
         Context context = getContext();
         Resources resources = getResources();
 
@@ -108,33 +115,34 @@ public class DomainSettingsFragment extends Fragment {
         boolean defaultWideViewport = sharedPreferences.getBoolean("wide_viewport", true);
         boolean defaultDisplayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
 
-        // Get handles for the views in the fragment.
+        // Get handles for the views.
+        ScrollView domainSettingsScrollView = domainSettingsView.findViewById(R.id.domain_settings_scrollview);
         EditText domainNameEditText = domainSettingsView.findViewById(R.id.domain_settings_name_edittext);
         ImageView javaScriptImageView = domainSettingsView.findViewById(R.id.javascript_imageview);
-        Switch javaScriptSwitch = domainSettingsView.findViewById(R.id.javascript_switch);
+        SwitchCompat javaScriptSwitch = domainSettingsView.findViewById(R.id.javascript_switch);
         ImageView firstPartyCookiesImageView = domainSettingsView.findViewById(R.id.first_party_cookies_imageview);
-        Switch firstPartyCookiesSwitch = domainSettingsView.findViewById(R.id.first_party_cookies_switch);
+        SwitchCompat firstPartyCookiesSwitch = domainSettingsView.findViewById(R.id.first_party_cookies_switch);
         LinearLayout thirdPartyCookiesLinearLayout = domainSettingsView.findViewById(R.id.third_party_cookies_linearlayout);
         ImageView thirdPartyCookiesImageView = domainSettingsView.findViewById(R.id.third_party_cookies_imageview);
-        Switch thirdPartyCookiesSwitch = domainSettingsView.findViewById(R.id.third_party_cookies_switch);
+        SwitchCompat thirdPartyCookiesSwitch = domainSettingsView.findViewById(R.id.third_party_cookies_switch);
         ImageView domStorageImageView = domainSettingsView.findViewById(R.id.dom_storage_imageview);
-        Switch domStorageSwitch = domainSettingsView.findViewById(R.id.dom_storage_switch);
+        SwitchCompat domStorageSwitch = domainSettingsView.findViewById(R.id.dom_storage_switch);
         ImageView formDataImageView = domainSettingsView.findViewById(R.id.form_data_imageview);  // The form data views can be remove once the minimum API >= 26.
-        Switch formDataSwitch = domainSettingsView.findViewById(R.id.form_data_switch);  // The form data views can be remove once the minimum API >= 26.
+        SwitchCompat formDataSwitch = domainSettingsView.findViewById(R.id.form_data_switch);  // The form data views can be remove once the minimum API >= 26.
         ImageView easyListImageView = domainSettingsView.findViewById(R.id.easylist_imageview);
-        Switch easyListSwitch = domainSettingsView.findViewById(R.id.easylist_switch);
+        SwitchCompat easyListSwitch = domainSettingsView.findViewById(R.id.easylist_switch);
         ImageView easyPrivacyImageView = domainSettingsView.findViewById(R.id.easyprivacy_imageview);
-        Switch easyPrivacySwitch = domainSettingsView.findViewById(R.id.easyprivacy_switch);
+        SwitchCompat easyPrivacySwitch = domainSettingsView.findViewById(R.id.easyprivacy_switch);
         ImageView fanboysAnnoyanceListImageView = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_imageview);
-        Switch fanboysAnnoyanceListSwitch = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_switch);
+        SwitchCompat fanboysAnnoyanceListSwitch = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_switch);
         ImageView fanboysSocialBlockingListImageView = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_imageview);
-        Switch fanboysSocialBlockingListSwitch = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_switch);
+        SwitchCompat fanboysSocialBlockingListSwitch = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_switch);
         ImageView ultraListImageView = domainSettingsView.findViewById(R.id.ultralist_imageview);
-        Switch ultraListSwitch = domainSettingsView.findViewById(R.id.ultralist_switch);
+        SwitchCompat ultraListSwitch = domainSettingsView.findViewById(R.id.ultralist_switch);
         ImageView ultraPrivacyImageView = domainSettingsView.findViewById(R.id.ultraprivacy_imageview);
-        Switch ultraPrivacySwitch = domainSettingsView.findViewById(R.id.ultraprivacy_switch);
+        SwitchCompat ultraPrivacySwitch = domainSettingsView.findViewById(R.id.ultraprivacy_switch);
         ImageView blockAllThirdPartyRequestsImageView = domainSettingsView.findViewById(R.id.block_all_third_party_requests_imageview);
-        Switch blockAllThirdPartyRequestsSwitch = domainSettingsView.findViewById(R.id.block_all_third_party_requests_switch);
+        SwitchCompat blockAllThirdPartyRequestsSwitch = domainSettingsView.findViewById(R.id.block_all_third_party_requests_switch);
         Spinner userAgentSpinner = domainSettingsView.findViewById(R.id.user_agent_spinner);
         TextView userAgentTextView = domainSettingsView.findViewById(R.id.user_agent_textview);
         EditText customUserAgentEditText = domainSettingsView.findViewById(R.id.custom_user_agent_edittext);
@@ -154,7 +162,7 @@ public class DomainSettingsFragment extends Fragment {
         Spinner displayWebpageImagesSpinner = domainSettingsView.findViewById(R.id.display_webpage_images_spinner);
         TextView displayImagesTextView = domainSettingsView.findViewById(R.id.display_webpage_images_textview);
         ImageView pinnedSslCertificateImageView = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_imageview);
-        Switch pinnedSslCertificateSwitch = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_switch);
+        SwitchCompat pinnedSslCertificateSwitch = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_switch);
         CardView savedSslCardView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_cardview);
         LinearLayout savedSslCertificateLinearLayout = domainSettingsView.findViewById(R.id.saved_ssl_certificate_linearlayout);
         RadioButton savedSslCertificateRadioButton = domainSettingsView.findViewById(R.id.saved_ssl_certificate_radiobutton);
@@ -179,7 +187,7 @@ public class DomainSettingsFragment extends Fragment {
         TextView currentSslEndDateTextView = domainSettingsView.findViewById(R.id.current_website_certificate_end_date);
         TextView noCurrentWebsiteCertificateTextView = domainSettingsView.findViewById(R.id.no_current_website_certificate);
         ImageView pinnedIpAddressesImageView = domainSettingsView.findViewById(R.id.pinned_ip_addresses_imageview);
-        Switch pinnedIpAddressesSwitch = domainSettingsView.findViewById(R.id.pinned_ip_addresses_switch);
+        SwitchCompat pinnedIpAddressesSwitch = domainSettingsView.findViewById(R.id.pinned_ip_addresses_switch);
         CardView savedIpAddressesCardView = domainSettingsView.findViewById(R.id.saved_ip_addresses_cardview);
         LinearLayout savedIpAddressesLinearLayout = domainSettingsView.findViewById(R.id.saved_ip_addresses_linearlayout);
         RadioButton savedIpAddressesRadioButton = domainSettingsView.findViewById(R.id.saved_ip_addresses_radiobutton);
@@ -368,24 +376,24 @@ public class DomainSettingsFragment extends Fragment {
         // Set the JavaScript switch status.
         if (javaScriptInt == 1) {  // JavaScript is enabled.
             javaScriptSwitch.setChecked(true);
-            javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled));
+            javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.javascript_enabled, null));
         } else {  // JavaScript is disabled.
             javaScriptSwitch.setChecked(false);
-            javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode));
+            javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.privacy_mode, null));
         }
 
         // Set the first-party cookies status.  Once the minimum API >= 21 a selector can be used as the tint mode instead of specifying different icons.
         if (firstPartyCookiesInt == 1) {  // First-party cookies are enabled.
             firstPartyCookiesSwitch.setChecked(true);
-            firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_enabled));
+            firstPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_enabled, null));
         } else {  // First-party cookies are disabled.
             firstPartyCookiesSwitch.setChecked(false);
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_night));
+                firstPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_night, null));
             } else {
-                firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_day));
+                firstPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_day, null));
             }
         }
 
@@ -399,16 +407,16 @@ public class DomainSettingsFragment extends Fragment {
                     thirdPartyCookiesSwitch.setChecked(true);
 
                     // Set the icon to be red.
-                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning));
+                    thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_warning, null));
                 } else {  // First party cookies are enabled but third-party cookies are disabled.
                     // Set the third-party cookies switch to be checked.
                     thirdPartyCookiesSwitch.setChecked(false);
 
                     // Set the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_night));
+                        thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_night, null));
                     } else {
-                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_day));
+                        thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_day, null));
                     }
                 }
             } else {  // First-party cookies are disabled.
@@ -424,9 +432,9 @@ public class DomainSettingsFragment extends Fragment {
 
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_night));
+                    thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_ghosted_night, null));
                 } else {
-                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_day));
+                    thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_ghosted_day, null));
                 }
             }
         } else {  // Third-party cookies cannot be configured for API <= 21.
@@ -442,16 +450,16 @@ public class DomainSettingsFragment extends Fragment {
             // Set the DOM storage status.  Once the minimum API >= 21 a selector can be used as the tint mode instead of specifying different icons.
             if (domStorageInt == 1) {  // Both JavaScript and DOM storage are enabled.
                 domStorageSwitch.setChecked(true);
-                domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled));
+                domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_enabled, null));
             } else {  // JavaScript is enabled but DOM storage is disabled.
                 // Set the DOM storage switch to off.
                 domStorageSwitch.setChecked(false);
 
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_night));
+                    domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_disabled_night, null));
                 } else {
-                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_day));
+                    domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_disabled_day, null));
                 }
             }
         } else {  // JavaScript is disabled.
@@ -467,9 +475,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_night));
+                domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_ghosted_night, null));
             } else {
-                domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_day));
+                domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_ghosted_day, null));
             }
         }
 
@@ -484,16 +492,16 @@ public class DomainSettingsFragment extends Fragment {
                 formDataSwitch.setChecked(true);
 
                 // Set the form data icon.
-                formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_enabled));
+                formDataImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.form_data_enabled, null));
             } else {  // Form data is off.
                 // Turn the form data switch to off.
                 formDataSwitch.setChecked(false);
 
                 // Set the icon according to the theme.  Once the minimum API >= 21 a selector can be used as the tint mode instead of specifying different icons.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_night));
+                    formDataImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.form_data_disabled_night, null));
                 } else {
-                    formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_day));
+                    formDataImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.form_data_disabled_day, null));
                 }
             }
         }
@@ -505,9 +513,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                easyListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_enabled_night));
+                easyListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_enabled_night, null));
             } else {
-                easyListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_enabled_day));
+                easyListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_enabled_day, null));
             }
         } else {  // EasyList is off.
             // Turn the switch off.
@@ -515,9 +523,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                easyListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_disabled_night));
+                easyListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_disabled_night, null));
             } else {
-                easyListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_disabled_day));
+                easyListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_disabled_day, null));
             }
         }
 
@@ -528,9 +536,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                easyPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_enabled_night));
+                easyPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_enabled_night, null));
             } else {
-                easyPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_enabled_day));
+                easyPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_enabled_day, null));
             }
         } else {  // EasyPrivacy is off.
             // Turn the switch off.
@@ -538,9 +546,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                easyPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_disabled_night));
+                easyPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_disabled_night, null));
             } else {
-                easyPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_disabled_day));
+                easyPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_disabled_day, null));
             }
         }
 
@@ -551,9 +559,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                fanboysAnnoyanceListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_night));
+                fanboysAnnoyanceListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_night, null));
             } else {
-                fanboysAnnoyanceListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_day));
+                fanboysAnnoyanceListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_day, null));
             }
         } else {  // Fanboy's Annoyance List is off.
             // Turn the switch off.
@@ -561,9 +569,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                fanboysAnnoyanceListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_night));
+                fanboysAnnoyanceListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_night, null));
             } else {
-                fanboysAnnoyanceListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_day));
+                fanboysAnnoyanceListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_day, null));
             }
         }
 
@@ -579,9 +587,9 @@ public class DomainSettingsFragment extends Fragment {
 
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_night));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_night, null));
                 } else {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_day));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_day, null));
                 }
             } else {  // Fanboy's Social Blocking List is off.
                 // Turn off Fanboy's Social Blocking List switch.
@@ -589,9 +597,9 @@ public class DomainSettingsFragment extends Fragment {
 
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_night));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_night, null));
                 } else {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_day));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_day, null));
                 }
             }
         } else {  // Fanboy's Annoyance List is on.
@@ -609,9 +617,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_ghosted_night));
+                fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_ghosted_night, null));
             } else {
-                fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_ghosted_day));
+                fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_ghosted_day, null));
             }
         }
 
@@ -622,9 +630,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                ultraListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_enabled_night));
+                ultraListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_enabled_night, null));
             } else {
-                ultraListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_enabled_day));
+                ultraListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_enabled_day, null));
             }
         } else {  // UltraList is off.
             // Turn the switch off.
@@ -632,9 +640,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                ultraListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_disabled_night));
+                ultraListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_disabled_night, null));
             } else {
-                ultraListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_disabled_day));
+                ultraListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_disabled_day, null));
             }
         }
 
@@ -645,9 +653,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                ultraPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_enabled_night));
+                ultraPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_enabled_night, null));
             } else {
-                ultraPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_enabled_day));
+                ultraPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_enabled_day, null));
             }
         } else {  // EasyPrivacy is off.
             // Turn the switch off.
@@ -655,9 +663,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                ultraPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_disabled_night));
+                ultraPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_disabled_night, null));
             } else {
-                ultraPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_disabled_day));
+                ultraPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_disabled_day, null));
             }
         }
 
@@ -668,9 +676,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                blockAllThirdPartyRequestsImageView.setImageDrawable(resources.getDrawable(R.drawable.block_all_third_party_requests_enabled_night));
+                blockAllThirdPartyRequestsImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_all_third_party_requests_enabled_night, null));
             } else {
-                blockAllThirdPartyRequestsImageView.setImageDrawable(resources.getDrawable(R.drawable.block_all_third_party_requests_enabled_day));
+                blockAllThirdPartyRequestsImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_all_third_party_requests_enabled_day, null));
             }
         } else {  // Blocking all third-party requests is off.
             // Turn the switch off.
@@ -678,9 +686,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                blockAllThirdPartyRequestsImageView.setImageDrawable(resources.getDrawable(R.drawable.block_all_third_party_requests_disabled_night));
+                blockAllThirdPartyRequestsImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_all_third_party_requests_disabled_night, null));
             } else {
-                blockAllThirdPartyRequestsImageView.setImageDrawable(resources.getDrawable(R.drawable.block_all_third_party_requests_disabled_day));
+                blockAllThirdPartyRequestsImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_all_third_party_requests_disabled_day, null));
             }
         }
 
@@ -814,16 +822,16 @@ public class DomainSettingsFragment extends Fragment {
                 if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled by default.
                     // Set the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_night));
+                        swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_enabled_night, null));
                     } else {
-                        swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_day));
+                        swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_enabled_day, null));
                     }
                 } else {  // Swipe to refresh is disabled by default
                     // Set the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_disabled_night));
+                        swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_disabled_night, null));
                     } else {
-                        swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_disabled_day));
+                        swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_disabled_day, null));
                     }
                 }
 
@@ -834,9 +842,9 @@ public class DomainSettingsFragment extends Fragment {
             case DomainsDatabaseHelper.ENABLED:
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_night));
+                    swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_enabled_night, null));
                 } else {
-                    swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_day));
+                    swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_enabled_day, null));
                 }
 
                 // Hide the swipe to refresh TextView.`
@@ -846,13 +854,14 @@ public class DomainSettingsFragment extends Fragment {
             case DomainsDatabaseHelper.DISABLED:
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_disabled_night));
+                    swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_disabled_night, null));
                 } else {
-                    swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_disabled_day));
+                    swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_disabled_day, null));
                 }
 
                 // Hide the swipe to refresh TextView.
                 swipeToRefreshTextView.setVisibility(View.GONE);
+                break;
         }
 
         // Open the swipe to refresh spinner when the TextView is clicked.
@@ -913,29 +922,28 @@ public class DomainSettingsFragment extends Fragment {
                             // Set the icon according to the app theme.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                                 // Set the light mode icon.
-                                webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_day));
+                                webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_day, null));
                             } else {
                                 // Set the dark theme icon.
-                                webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_night));
+                                webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_night, null));
                             }
                             break;
 
                         case DomainsDatabaseHelper.LIGHT_THEME:  // the default WebView theme is light.
                             // Set the icon according to the app theme.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                                webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_day));
+                                webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_day, null));
                             } else {
-                                webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_night));
+                                webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_night, null));
                             }
                             break;
 
                         case DomainsDatabaseHelper.DARK_THEME:  // the default WebView theme is dark.
                             // Set the icon according to the app theme.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                                webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_day));
+                                webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_day, null));
                             } else {
-                                webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_night));
-                            }
+                                webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_night, null));                            }
                             break;
                     }
 
@@ -946,9 +954,9 @@ public class DomainSettingsFragment extends Fragment {
                 case DomainsDatabaseHelper.LIGHT_THEME:  // The domain WebView theme is light.
                     // Set the icon according to the app theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                        webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_day));
+                        webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_day, null));
                     } else {
-                        webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_night));
+                        webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_night, null));
                     }
 
                     // Hide the WebView theme text view.
@@ -958,9 +966,9 @@ public class DomainSettingsFragment extends Fragment {
                 case DomainsDatabaseHelper.DARK_THEME:  // The domain WebView theme is dark.
                     // Set the icon according to the app theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                        webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_day));
+                        webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_day, null));
                     } else {
-                        webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_night));
+                        webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_night, null));
                     }
 
                     // Hide the WebView theme text view.
@@ -991,15 +999,15 @@ public class DomainSettingsFragment extends Fragment {
                 if (defaultWideViewport) {  // Wide viewport enabled by default.
                     // Set the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_enabled_night));
+                        wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_enabled_night, null));
                     } else {
-                        wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_enabled_day));
+                        wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_enabled_day, null));
                     }
                 } else {  // Wide viewport disabled by default.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_disabled_night));
+                        wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_disabled_night, null));
                     } else {
-                        wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_disabled_day));
+                        wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_disabled_day, null));
                     }
                 }
 
@@ -1010,9 +1018,9 @@ public class DomainSettingsFragment extends Fragment {
             case DomainsDatabaseHelper.ENABLED:
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_enabled_night));
+                    wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_enabled_night, null));
                 } else {
-                    wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_enabled_day));
+                    wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_enabled_day, null));
                 }
 
                 // Hide the wide viewport text view.
@@ -1022,9 +1030,9 @@ public class DomainSettingsFragment extends Fragment {
             case DomainsDatabaseHelper.DISABLED:
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_disabled_night));
+                    wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_disabled_night, null));
                 } else {
-                    wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_disabled_day));
+                    wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_disabled_day, null));
                 }
 
                 // Hide the wide viewport text view.
@@ -1054,16 +1062,16 @@ public class DomainSettingsFragment extends Fragment {
                 if (defaultDisplayWebpageImages) {  // Display webpage images enabled by default.
                     // Set the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_night));
+                        displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_enabled_night, null));
                     } else {
-                        displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_day));
+                        displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_enabled_day, null));
                     }
                 } else {  // Display webpage images disabled by default.
                     // Set the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_night));
+                        displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_disabled_night, null));
                     } else {
-                        displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_day));
+                        displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_disabled_day, null));
                     }
                 }
 
@@ -1074,9 +1082,9 @@ public class DomainSettingsFragment extends Fragment {
             case DomainsDatabaseHelper.ENABLED:
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_night));
+                    displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_enabled_night, null));
                 } else {
-                    displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_day));
+                    displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_enabled_day, null));
                 }
 
                 // Hide the display images text view.
@@ -1086,9 +1094,9 @@ public class DomainSettingsFragment extends Fragment {
             case DomainsDatabaseHelper.DISABLED:
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_night));
+                    displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_disabled_night, null));
                 } else {
-                    displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_day));
+                    displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_disabled_day, null));
                 }
 
                 // Hide the display images text view.
@@ -1109,9 +1117,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_night));
+                pinnedSslCertificateImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_enabled_night, null));
             } else {
-                pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_day));
+                pinnedSslCertificateImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_enabled_day, null));
             }
         } else {  // Pinned SSL certificate is disabled.
             // Uncheck the switch.
@@ -1119,9 +1127,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_night));
+                pinnedSslCertificateImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_disabled_night, null));
             } else {
-                pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_day));
+                pinnedSslCertificateImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_disabled_day, null));
             }
         }
 
@@ -1296,9 +1304,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_night));
+                pinnedIpAddressesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_enabled_night, null));
             } else {
-                pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_day));
+                pinnedIpAddressesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_enabled_day, null));
             }
         } else {  // Pinned IP Addresses is disabled.
             // Uncheck the switch.
@@ -1306,9 +1314,9 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_night));
+                pinnedIpAddressesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_disabled_night, null));
             } else {
-                pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_day));
+                pinnedIpAddressesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_disabled_day, null));
             }
         }
 
@@ -1364,34 +1372,34 @@ public class DomainSettingsFragment extends Fragment {
         javaScriptSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
             if (isChecked) {  // JavaScript is enabled.
                 // Update the JavaScript icon.
-                javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled));
+                javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.javascript_enabled, null));
 
                 // Enable the DOM storage `Switch`.
                 domStorageSwitch.setEnabled(true);
 
                 // Update the DOM storage icon.
                 if (domStorageSwitch.isChecked()) {  // DOM storage is enabled.
-                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled));
+                    domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_enabled, null));
                 } else {  // DOM storage is disabled.
                     // Set the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_night));
+                        domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_disabled_night, null));
                     } else {
-                        domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_day));
+                        domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_disabled_day, null));
                     }
                 }
             } else {  // JavaScript is disabled.
                 // Update the JavaScript icon.
-                javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode));
+                javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.privacy_mode, null));
 
                 // Disable the DOM storage `Switch`.
                 domStorageSwitch.setEnabled(false);
 
                 // Set the DOM storage icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_night));
+                    domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_ghosted_night, null));
                 } else {
-                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_day));
+                    domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_ghosted_day, null));
                 }
             }
         });
@@ -1400,7 +1408,7 @@ public class DomainSettingsFragment extends Fragment {
         firstPartyCookiesSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
             if (isChecked) {  // First-party cookies are enabled.
                 // Update the first-party cookies icon.
-                firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_enabled));
+                firstPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_enabled, null));
 
                 // Enable the third-party cookies switch.
                 thirdPartyCookiesSwitch.setEnabled(true);
@@ -1408,21 +1416,21 @@ public class DomainSettingsFragment extends Fragment {
                 // Update the third-party cookies icon.
                 if (thirdPartyCookiesSwitch.isChecked()) {  // Third-party cookies are enabled.
                     // Set the third-party cookies icon to be red.
-                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning));
+                    thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_warning, null));
                 } else {  // Third-party cookies are disabled.
                     // Set the third-party cookies icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_night));
+                        thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_night, null));
                     } else {
-                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_day));
+                        thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_day, null));
                     }
                 }
             } else {  // First-party cookies are disabled.
                 // Update the first-party cookies icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_night));
+                    firstPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_night, null));
                 } else {
-                    firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_day));
+                    firstPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_day, null));
                 }
 
                 // Disable the third-party cookies switch.
@@ -1430,9 +1438,9 @@ public class DomainSettingsFragment extends Fragment {
 
                 // Set the third-party cookies icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_night));
+                    thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_ghosted_night, null));
                 } else {
-                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_day));
+                    thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_ghosted_day, null));
                 }
             }
         });
@@ -1442,13 +1450,13 @@ public class DomainSettingsFragment extends Fragment {
             // Update the icon.
             if (isChecked) {
                 // Set the third-party cookies icon to be red.
-                thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning));
+                thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_warning, null));
             } else {
                 // Update the third-party cookies icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_night));
+                    thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_night, null));
                 } else {
-                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_day));
+                    thirdPartyCookiesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.cookies_disabled_day, null));
                 }
             }
         });
@@ -1457,13 +1465,13 @@ public class DomainSettingsFragment extends Fragment {
         domStorageSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
             // Update the icon.
             if (isChecked) {
-                domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled));
+                domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_enabled, null));
             } else {
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_night));
+                    domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_disabled_night, null));
                 } else {
-                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_day));
+                    domStorageImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.dom_storage_disabled_day, null));
                 }
             }
         });
@@ -1473,13 +1481,13 @@ public class DomainSettingsFragment extends Fragment {
             formDataSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
                 // Update the icon.
                 if (isChecked) {
-                    formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_enabled));
+                    formDataImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.form_data_enabled, null));
                 } else {
                     // Set the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_night));
+                        formDataImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.form_data_disabled_night, null));
                     } else {
-                        formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_day));
+                        formDataImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.form_data_disabled_day, null));
                     }
                 }
             });
@@ -1491,16 +1499,16 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // EasyList is on.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    easyListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_enabled_night));
+                    easyListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_enabled_night, null));
                 } else {
-                    easyListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_enabled_day));
+                    easyListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_enabled_day, null));
                 }
             } else {  // EasyList is off.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    easyListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_disabled_night));
+                    easyListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_disabled_night, null));
                 } else {
-                    easyListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_disabled_day));
+                    easyListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_disabled_day, null));
                 }
             }
         });
@@ -1511,16 +1519,16 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // EasyPrivacy is on.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    easyPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_enabled_night));
+                    easyPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_enabled_night, null));
                 } else {
-                    easyPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_enabled_day));
+                    easyPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_enabled_day, null));
                 }
             } else {  // EasyPrivacy is off.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    easyPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_disabled_night));
+                    easyPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_disabled_night, null));
                 } else {
-                    easyPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_disabled_day));
+                    easyPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_disabled_day, null));
                 }
             }
         });
@@ -1531,9 +1539,9 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // Fanboy's Annoyance List is on.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    fanboysAnnoyanceListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_night));
+                    fanboysAnnoyanceListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_night, null));
                 } else {
-                    fanboysAnnoyanceListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_day));
+                    fanboysAnnoyanceListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_day, null));
                 }
 
                 // Disable the Fanboy's Social Blocking List switch.
@@ -1541,16 +1549,16 @@ public class DomainSettingsFragment extends Fragment {
 
                 // Update the Fanboy's Social Blocking List icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_ghosted_night));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_ghosted_night, null));
                 } else {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_ghosted_day));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_ghosted_day, null));
                 }
             } else {  // Fanboy's Annoyance List is off.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    fanboysAnnoyanceListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_night));
+                    fanboysAnnoyanceListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_night, null));
                 } else {
-                    fanboysAnnoyanceListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_day));
+                    fanboysAnnoyanceListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_day, null));
                 }
 
                 // Enable the Fanboy's Social Blocking List switch.
@@ -1560,16 +1568,16 @@ public class DomainSettingsFragment extends Fragment {
                 if (fanboysSocialBlockingListSwitch.isChecked()) {  // Fanboy's Social Blocking List is on.
                     // Update the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_night));
+                        fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_night, null));
                     } else {
-                        fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_day));
+                        fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_day, null));
                     }
                 } else {  // Fanboy's Social Blocking List is off.
                     // Update the icon according to the theme.
                     if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                        fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_night));
+                        fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_night, null));
                     } else {
-                        fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_day));
+                        fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_day, null));
                     }
                 }
             }
@@ -1582,16 +1590,16 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // Fanboy's Social Blocking List is on.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_night));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_night, null));
                 } else {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_enabled_day));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_enabled_day, null));
                 }
             } else {  // Fanboy's Social Blocking List is off.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_night));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_night, null));
                 } else {
-                    fanboysSocialBlockingListImageView.setImageDrawable(resources.getDrawable(R.drawable.social_media_disabled_day));
+                    fanboysSocialBlockingListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.social_media_disabled_day, null));
                 }
             }
         });
@@ -1602,16 +1610,16 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // UltraList is on.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    ultraListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_enabled_night));
+                    ultraListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_enabled_night, null));
                 } else {
-                    ultraListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_enabled_day));
+                    ultraListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_enabled_day, null));
                 }
             } else {  // UltraList is off.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    ultraListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_disabled_night));
+                    ultraListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_disabled_night, null));
                 } else {
-                    ultraListImageView.setImageDrawable(resources.getDrawable(R.drawable.block_ads_disabled_day));
+                    ultraListImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_ads_disabled_day, null));
                 }
             }
         });
@@ -1622,16 +1630,16 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // UltraPrivacy is on.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    ultraPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_enabled_night));
+                    ultraPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_enabled_night, null));
                 } else {
-                    ultraPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_enabled_day));
+                    ultraPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_enabled_day, null));
                 }
             } else {  // UltraPrivacy is off.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    ultraPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_disabled_night));
+                    ultraPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_disabled_night, null));
                 } else {
-                    ultraPrivacyImageView.setImageDrawable(resources.getDrawable(R.drawable.block_tracking_disabled_day));
+                    ultraPrivacyImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_tracking_disabled_day, null));
                 }
             }
         });
@@ -1642,16 +1650,16 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // Blocking all third-party requests is on.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    blockAllThirdPartyRequestsImageView.setImageDrawable(resources.getDrawable(R.drawable.block_all_third_party_requests_enabled_night));
+                    blockAllThirdPartyRequestsImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_all_third_party_requests_enabled_night, null));
                 } else {
-                    blockAllThirdPartyRequestsImageView.setImageDrawable(resources.getDrawable(R.drawable.block_all_third_party_requests_enabled_day));
+                    blockAllThirdPartyRequestsImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_all_third_party_requests_enabled_day, null));
                 }
             } else {  // Blocking all third-party requests is off.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    blockAllThirdPartyRequestsImageView.setImageDrawable(resources.getDrawable(R.drawable.block_all_third_party_requests_disabled_night));
+                    blockAllThirdPartyRequestsImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_all_third_party_requests_disabled_night, null));
                 } else {
-                    blockAllThirdPartyRequestsImageView.setImageDrawable(resources.getDrawable(R.drawable.block_all_third_party_requests_disabled_day));
+                    blockAllThirdPartyRequestsImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.block_all_third_party_requests_disabled_day, null));
                 }
             }
         });
@@ -1762,16 +1770,16 @@ public class DomainSettingsFragment extends Fragment {
                         if (defaultSwipeToRefresh) {  // Swipe to refresh enabled by default.
                             // Set the icon according to the theme.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                                swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_night));
+                                swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_enabled_night, null));
                             } else {
-                                swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_day));
+                                swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_enabled_day, null));
                             }
                         } else {  // Swipe to refresh disabled by default.
                             // Set the icon according to the theme.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                                swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_disabled_night));
+                                swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_disabled_night, null));
                             } else {
-                                swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_disabled_day));
+                                swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_disabled_day, null));
                             }
                         }
 
@@ -1782,9 +1790,9 @@ public class DomainSettingsFragment extends Fragment {
                     case DomainsDatabaseHelper.ENABLED:
                         // Set the icon according to the theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                            swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_night));
+                            swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_enabled_night, null));
                         } else {
-                            swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_day));
+                            swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_enabled_day, null));
                         }
 
                         // Hide the swipe to refresh TextView.
@@ -1794,9 +1802,9 @@ public class DomainSettingsFragment extends Fragment {
                     case DomainsDatabaseHelper.DISABLED:
                         // Set the icon according to the theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                            swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_disabled_night));
+                            swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_disabled_night, null));
                         } else {
-                            swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_disabled_day));
+                            swipeToRefreshImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.refresh_disabled_day, null));
                         }
 
                         // Hide the swipe to refresh TextView.
@@ -1823,28 +1831,28 @@ public class DomainSettingsFragment extends Fragment {
                                 // Set the icon according to the app theme.
                                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                                     // Set the light mode icon.
-                                    webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_day));
+                                    webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_day, null));
                                 } else {
                                     // Set the dark theme icon.
-                                    webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_night));
+                                    webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_night, null));
                                 }
                                 break;
 
                             case DomainsDatabaseHelper.LIGHT_THEME:  // The default WebView theme is light.
                                 // Set the icon according to the app theme.
                                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                                    webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_day));
+                                    webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_day, null));
                                 } else {
-                                    webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_night));
+                                    webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_night, null));
                                 }
                                 break;
 
                             case DomainsDatabaseHelper.DARK_THEME:  // The default WebView theme is dark.
                                 // Set the icon according to the app theme.
                                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                                    webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_day));
+                                    webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_day, null));
                                 } else {
-                                    webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_night));
+                                    webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_night, null));
                                 }
                                 break;
                         }
@@ -1856,9 +1864,9 @@ public class DomainSettingsFragment extends Fragment {
                     case DomainsDatabaseHelper.LIGHT_THEME:  // The domain WebView theme is light.
                         // Set the icon according to the app theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                            webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_day));
+                            webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_day, null));
                         } else {
-                            webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_light_theme_night));
+                            webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_light_theme_night, null));
                         }
 
                         // Hide the WebView theme text view.
@@ -1868,9 +1876,9 @@ public class DomainSettingsFragment extends Fragment {
                     case DomainsDatabaseHelper.DARK_THEME:  // The domain WebView theme is dark.
                         // Set the icon according to the app theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                            webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_day));
+                            webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_day, null));
                         } else {
-                            webViewThemeImageView.setImageDrawable(resources.getDrawable(R.drawable.webview_dark_theme_night));
+                            webViewThemeImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.webview_dark_theme_night, null));
                         }
 
                         // Hide the WebView theme text view.
@@ -1895,15 +1903,15 @@ public class DomainSettingsFragment extends Fragment {
                         if (defaultWideViewport) {  // Wide viewport is enabled by default.
                             // Set the icon according to the theme.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                                wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_enabled_night));
+                                wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_enabled_night, null));
                             } else {
-                                wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_enabled_day));
+                                wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_enabled_day, null));
                             }
                         } else {  // Wide viewport is disabled by default.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                                wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_disabled_night));
+                                wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_disabled_night, null));
                             } else {
-                                wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_disabled_day));
+                                wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_disabled_day, null));
                             }
                         }
 
@@ -1914,9 +1922,9 @@ public class DomainSettingsFragment extends Fragment {
                     case DomainsDatabaseHelper.ENABLED:
                         // Set the icon according to the theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                            wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_enabled_night));
+                            wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_enabled_night, null));
                         } else {
-                            wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_enabled_day));
+                            wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_enabled_day, null));
                         }
 
                         // Hide the wide viewport text view.
@@ -1926,9 +1934,9 @@ public class DomainSettingsFragment extends Fragment {
                     case DomainsDatabaseHelper.DISABLED:
                         // Set the icon according to the theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                            wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_disabled_night));
+                            wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_disabled_night, null));
                         } else {
-                            wideViewportImageView.setImageDrawable(resources.getDrawable(R.drawable.wide_viewport_disabled_day));
+                            wideViewportImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.wide_viewport_disabled_day, null));
                         }
 
                         // Hid ethe wide viewport text view.
@@ -1953,16 +1961,16 @@ public class DomainSettingsFragment extends Fragment {
                         if (defaultDisplayWebpageImages) {  // Display webpage images is enabled by default.
                             // Set the icon according to the theme.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                                displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_night));
+                                displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_enabled_night, null));
                             } else {
-                                displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_day));
+                                displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_enabled_day, null));
                             }
                         } else {  // Display webpage images is disabled by default.
                             // Set the icon according to the theme.
                             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                                displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_night));
+                                displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_disabled_night, null));
                             } else {
-                                displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_day));
+                                displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_disabled_day, null));
                             }
                         }
 
@@ -1973,9 +1981,9 @@ public class DomainSettingsFragment extends Fragment {
                     case DomainsDatabaseHelper.ENABLED:
                         // Set the icon according to the theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                            displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_night));
+                            displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_enabled_night, null));
                         } else {
-                            displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_day));
+                            displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_enabled_day, null));
                         }
 
                         // Hide the display images text view.
@@ -1985,9 +1993,9 @@ public class DomainSettingsFragment extends Fragment {
                     case DomainsDatabaseHelper.DISABLED:
                         // Set the icon according to the theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                            displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_night));
+                            displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_disabled_night, null));
                         } else {
-                            displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_day));
+                            displayWebpageImagesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.images_disabled_day, null));
                         }
 
                         // Hide the display images text view.
@@ -2008,9 +2016,9 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // SSL certificate pinning is enabled.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_night));
+                    pinnedSslCertificateImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_enabled_night, null));
                 } else {
-                    pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_day));
+                    pinnedSslCertificateImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_enabled_day, null));
                 }
 
                 // Update the visibility of the saved SSL certificate.
@@ -2085,9 +2093,9 @@ public class DomainSettingsFragment extends Fragment {
             } else {  // SSL certificate pinning is disabled.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_night));
+                    pinnedSslCertificateImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_disabled_night, null));
                 } else {
-                    pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_day));
+                    pinnedSslCertificateImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_disabled_day, null));
                 }
 
                 // Hide the SSl certificates and instructions.
@@ -2179,9 +2187,9 @@ public class DomainSettingsFragment extends Fragment {
             if (isChecked) {  // IP addresses pinning is enabled.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_night));
+                    pinnedIpAddressesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_enabled_night, null));
                 } else {
-                    pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_day));
+                    pinnedIpAddressesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_enabled_day, null));
                 }
 
                 // Update the visibility of the saved IP addresses card view.
@@ -2234,9 +2242,9 @@ public class DomainSettingsFragment extends Fragment {
             } else {  // IP addresses pinning is disabled.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
-                    pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_night));
+                    pinnedIpAddressesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_disabled_night, null));
                 } else {
-                    pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_day));
+                    pinnedIpAddressesImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ssl_certificate_disabled_day, null));
                 }
 
                 // Hide the IP addresses card views.
@@ -2321,6 +2329,10 @@ public class DomainSettingsFragment extends Fragment {
             }
         });
 
+        // Set the scroll Y.
+        domainSettingsScrollView.post(() -> domainSettingsScrollView.setScrollY(scrollY));
+
+        // Return the domain settings view.
         return domainSettingsView;
     }
 
index 2aa974b9f2a9576d6b037ad6e17986495da51a84..2d22d9c7225e26cbded4b03e62bcefb97383e2f2 100644 (file)
@@ -38,7 +38,7 @@ import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.activities.DomainsActivity;
 
 public class DomainsListFragment extends Fragment {
-    // Instantiate the dismiss snackbar interface handle.
+    // Instantiate the dismiss snackbar interface.
     private DismissSnackbarInterface dismissSnackbarInterface;
 
     // Define the public dismiss snackbar interface.
@@ -50,7 +50,7 @@ public class DomainsListFragment extends Fragment {
         // Run the default commands.
         super.onAttach(context);
 
-        // Get a handle for the dismiss snackbar interface.
+        // Populate the dismiss snackbar interface.
         dismissSnackbarInterface = (DismissSnackbarInterface) context;
     }
 
@@ -58,7 +58,7 @@ public class DomainsListFragment extends Fragment {
         // Inflate `domains_list_fragment`.  `false` does not attach it to the root `container`.
         View domainsListFragmentView = inflater.inflate(R.layout.domains_list_fragment, container, false);
 
-        // Initialize `domainsListView`.
+        // Get a handle for the domains listview.
         ListView domainsListView = domainsListFragmentView.findViewById(R.id.domains_listview);
 
         // Remove the incorrect lint error below that `.getSupportFragmentManager()` might be null.
@@ -103,7 +103,7 @@ public class DomainsListFragment extends Fragment {
             DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment();
             domainSettingsFragment.setArguments(argumentsBundle);
 
-            // Display the domain settings fragment.
+            // Check to see if the device is in two paned mode.
             if (DomainsActivity.twoPanedMode) {  // The device in in two-paned mode.
                 // enable `deleteMenuItem` if the system is not waiting for a `Snackbar` to be dismissed.
                 if (!DomainsActivity.dismissingSnackbar) {
@@ -124,6 +124,9 @@ public class DomainsListFragment extends Fragment {
                 // Display the domain settings fragment.
                 supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit();
             } else { // The device in in single-paned mode
+                // Save the domains listview position.
+                DomainsActivity.domainsListViewPosition = domainsListView.getFirstVisiblePosition();
+
                 // Show `deleteMenuItem` if the system is not waiting for a `Snackbar` to be dismissed.
                 if (!DomainsActivity.dismissingSnackbar) {
                     DomainsActivity.deleteMenuItem.setVisible(true);
@@ -138,6 +141,7 @@ public class DomainsListFragment extends Fragment {
             }
         });
 
+        // Return the domains list fragment.
         return domainsListFragmentView;
     }
 }
\ No newline at end of file
index 6a2e82f113621e106d86e6a5c04471845843913e..6f3aa0d3c26b30bac6c3b3746124842038af0b8f 100644 (file)
@@ -19,7 +19,6 @@
 
 package com.stoutner.privacybrowser.fragments;
 
-import android.annotation.SuppressLint;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.view.LayoutInflater;
@@ -33,19 +32,22 @@ import androidx.fragment.app.Fragment;
 import com.stoutner.privacybrowser.R;
 
 public class GuideTabFragment extends Fragment {
-    // `tabNumber` is used in `onCreate()` and `onCreateView()`.
+    // Define the class variables.
     private int tabNumber;
+    private View tabLayout;
 
     // Store the tab number in the arguments bundle.
-    public static GuideTabFragment createTab (int tab) {
+    public static GuideTabFragment createTab (int tabNumber) {
         // Create a bundle.
         Bundle bundle = new Bundle();
 
         // Store the tab number in the bundle.
-        bundle.putInt("Tab", tab);
+        bundle.putInt("tab_number", tabNumber);
 
-        // Add the bundle to the fragment.
+        // Create a new guide tab fragment.
         GuideTabFragment guideTabFragment = new GuideTabFragment();
+
+        // Add the bundle to the fragment.
         guideTabFragment.setArguments(bundle);
 
         // Return the new fragment.
@@ -57,18 +59,20 @@ public class GuideTabFragment extends Fragment {
         // Run the default commands.
         super.onCreate(savedInstanceState);
 
-        // Remove the lint warning that `getArguments()` might be null.
-        assert getArguments() != null;
+        // Get a handle for the arguments.
+        Bundle arguments = getArguments();
+
+        // Remove the lint warning below that arguments might be null.
+        assert arguments != null;
 
         // Store the tab number in a class variable.
-        tabNumber = getArguments().getInt("Tab");
+        tabNumber = arguments.getInt("tab_number");
     }
 
-    @SuppressLint("SetJavaScriptEnabled")
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.  The fragment will take care of attaching the root automatically.
-        View tabLayout = inflater.inflate(R.layout.bare_webview, container, false);
+        // Inflate the layout.  The fragment will take care of attaching the root automatically.
+        tabLayout = inflater.inflate(R.layout.bare_webview, container, false);
 
         // Get a handle for the tab WebView.
         WebView tabWebView = (WebView) tabLayout;
@@ -159,7 +163,26 @@ public class GuideTabFragment extends Fragment {
             }
         }
 
+        // Scroll the WebView if the saved instance state is not null.
+        if (savedInstanceState != null) {
+            tabWebView.post(() -> tabWebView.setScrollY(savedInstanceState.getInt("scroll_y")));
+        }
+
         // Return the formatted `tabLayout`.
         return tabLayout;
     }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onSaveInstanceState(savedInstanceState);
+
+        // Get a handle for the tab WebView.  A class variable cannot be used because it gets out of sync when restarting.
+        WebView tabWebView = (WebView) tabLayout;
+
+        // Save the scroll Y position if the tab WebView is not null, which can happen if a tab is not currently selected.
+        if (tabWebView != null) {
+            savedInstanceState.putInt("scroll_y", tabWebView.getScrollY());
+        }
+    }
 }
\ No newline at end of file
index 91866a3e397e72dacc945d318806ce98fc5c5744..1acf3b5412f216f728324c1cd3b33f3dce1c23f6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -40,12 +40,22 @@ public class WebViewTabFragment extends Fragment {
 
     // The public interface is used to send information back to the parent activity.
     public interface NewTabListener {
-        void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url);
+        void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState);
     }
 
     // The new tab listener is used in `onAttach()` and `onCreateView()`.
     private NewTabListener newTabListener;
 
+    // Define the bundle constants.
+    private final static String CREATE_NEW_PAGE = "create_new_page";
+    private final static String PAGE_NUMBER = "page_number";
+    private final static String URL = "url";
+    private final static String SAVED_STATE = "saved_state";
+    private final static String SAVED_NESTED_SCROLL_WEBVIEW_STATE = "saved_nested_scroll_webview_state";
+
+    // Define the class views.
+    NestedScrollWebView nestedScrollWebView;
+
     @Override
     public void onAttach(@NonNull Context context) {
         // Run the default commands.
@@ -56,49 +66,108 @@ public class WebViewTabFragment extends Fragment {
     }
 
     public static WebViewTabFragment createPage(int pageNumber, String url) {
-        // Create a bundle.
-        Bundle bundle = new Bundle();
+        // Create an arguments bundle.
+        Bundle argumentsBundle = new Bundle();
 
-        // Store the page number and URL in the bundle.
-        bundle.putInt("page_number", pageNumber);
-        bundle.putString("url", url);
+        // Store the argument in the bundle.
+        argumentsBundle.putBoolean(CREATE_NEW_PAGE, true);
+        argumentsBundle.putInt(PAGE_NUMBER, pageNumber);
+        argumentsBundle.putString(URL, url);
 
         // Create a new instance of the WebView tab fragment.
         WebViewTabFragment webViewTabFragment = new WebViewTabFragment();
 
-        // Add the bundle to the fragment.
-        webViewTabFragment.setArguments(bundle);
+        // Add the arguments bundle to the fragment.
+        webViewTabFragment.setArguments(argumentsBundle);
 
         // Return the new fragment.
         return webViewTabFragment;
     }
 
-    @Override
-    public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
-        // Get the arguments.
-        Bundle arguments = getArguments();
+    public static WebViewTabFragment restorePage(Bundle savedState, Bundle savedNestedScrollWebViewState) {
+        // Create an arguments bundle
+        Bundle argumentsBundle = new Bundle();
 
-        // Remove the incorrect lint warning that the arguments might be null.
-        assert arguments != null;
+        // Store the saved states in the arguments bundle.
+        argumentsBundle.putBundle(SAVED_STATE, savedState);
+        argumentsBundle.putBundle(SAVED_NESTED_SCROLL_WEBVIEW_STATE, savedNestedScrollWebViewState);
 
-        // Get the variables from the arguments
-        int pageNumber = arguments.getInt("page_number");
-        String url = arguments.getString("url");
-
-        // Inflate the tab's WebView.  Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.  The fragment will take care of attaching the root automatically.
-        View newPageView = layoutInflater.inflate(R.layout.webview_framelayout, container, false);
-
-        // Get handles for the views.
-        NestedScrollWebView nestedScrollWebView = newPageView.findViewById(R.id.nestedscroll_webview);
-        ProgressBar progressBar = newPageView.findViewById(R.id.progress_bar);
+        // Create a new instance of the WebView tab fragment.
+        WebViewTabFragment webViewTabFragment = new WebViewTabFragment();
 
-        // Store the WebView fragment ID in the nested scroll WebView.
-        nestedScrollWebView.setWebViewFragmentId(fragmentId);
+        // Add the arguments bundle to the fragment.
+        webViewTabFragment.setArguments(argumentsBundle);
 
-        // Request the main activity initialize the WebView.
-        newTabListener.initializeWebView(nestedScrollWebView, pageNumber, progressBar, url);
+        // Return the new fragment.
+        return webViewTabFragment;
+    }
 
-        // Return the new page view.
-        return newPageView;
+    @Override
+    public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
+        // Check to see if the fragment is being restarted.
+        if (savedInstanceState == null) {  // The fragment is not being restarted.  Load and configure a new fragment.
+            // Get the arguments.
+            Bundle arguments = getArguments();
+
+            // Remove the incorrect lint warning that the arguments might be null.
+            assert arguments != null;
+
+            // Check to see if a new page is being created.
+            if (arguments.getBoolean(CREATE_NEW_PAGE)) {  // A new page is being created.
+                // Get the variables from the arguments
+                int pageNumber = arguments.getInt(PAGE_NUMBER);
+                String url = arguments.getString(URL);
+
+                // Inflate the tab's WebView.  Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.
+                // The fragment will take care of attaching the root automatically.
+                View newPageView = layoutInflater.inflate(R.layout.webview_framelayout, container, false);
+
+                // Get handles for the views.
+                nestedScrollWebView = newPageView.findViewById(R.id.nestedscroll_webview);
+                ProgressBar progressBar = newPageView.findViewById(R.id.progress_bar);
+
+                // Store the WebView fragment ID in the nested scroll WebView.
+                nestedScrollWebView.setWebViewFragmentId(fragmentId);
+
+                // Request the main activity initialize the WebView.
+                newTabListener.initializeWebView(nestedScrollWebView, pageNumber, progressBar, url, false);
+
+                // Return the new page view.
+                return newPageView;
+            } else {  // A page is being restored.
+                // Get the saved states from the arguments.
+                Bundle savedState = arguments.getBundle(SAVED_STATE);
+                Bundle savedNestedScrollWebViewState = arguments.getBundle(SAVED_NESTED_SCROLL_WEBVIEW_STATE);
+
+                // Remove the incorrect lint warning below that the saved nested scroll WebView state might be null.
+                assert savedNestedScrollWebViewState != null;
+
+                // Inflate the tab's WebView.  Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.
+                // The fragment will take care of attaching the root automatically.
+                View newPageView = layoutInflater.inflate(R.layout.webview_framelayout, container, false);
+
+                // Get handles for the views.
+                nestedScrollWebView = newPageView.findViewById(R.id.nestedscroll_webview);
+                ProgressBar progressBar = newPageView.findViewById(R.id.progress_bar);
+
+                // Store the WebView fragment ID in the nested scroll WebView.
+                nestedScrollWebView.setWebViewFragmentId(fragmentId);
+
+                // Restore the nested scroll WebView state.
+                nestedScrollWebView.restoreNestedScrollWebViewState(savedNestedScrollWebViewState);
+
+                // Restore the WebView state.
+                nestedScrollWebView.restoreState(savedState);
+
+                // Initialize the WebView.
+                newTabListener.initializeWebView(nestedScrollWebView, 0, progressBar, null, true);
+
+                // Return the new page view.
+                return newPageView;
+            }
+        } else {  // The fragment is being restarted.
+            // Return null.  Otherwise, the fragment will be inflated and initialized by the OS on a restart, discarded, and then recreated with saved settings by Privacy Browser.
+            return null;
+        }
     }
 }
\ No newline at end of file
index 984402e826a077877b8117e0323d5a8e19a4b243..529a769b8d03bbf26539b59895ba35540f8a8bc5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -334,12 +334,14 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
 
         // Extract the array of IDs not to get to the string builder.
         for (long databaseIdLong : exceptIdLongArray) {
-            if (idsNotToGetStringBuilder.length() == 0) {  // This is the first number, so only add the number.
-                idsNotToGetStringBuilder.append(databaseIdLong);
-            } else {  // This is not the first number, so place a `,` before the new number.
+            // Check to see if there is already a number in the builder.
+            if (idsNotToGetStringBuilder.length() > 0) {
+                // This is not the first number, so place a `,` before the new number.
                 idsNotToGetStringBuilder.append(",");
-                idsNotToGetStringBuilder.append(databaseIdLong);
             }
+
+            // Add the new number to the builder.
+            idsNotToGetStringBuilder.append(databaseIdLong);
         }
 
         // Prepare the SQL statement to select all items except those with the specified IDs.
@@ -360,12 +362,14 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
 
         // Extract the array of IDs not to get to the string builder.
         for (long databaseIdLong : exceptIdLongArray) {
-            if (idsNotToGetStringBuilder.length() == 0) {  // This is the first number, so only add the number.
-                idsNotToGetStringBuilder.append(databaseIdLong);
-            } else {  // This is not the first number, so place a `,` before the new number.
+            // Check to see if there is already a number in the builder.
+            if (idsNotToGetStringBuilder.length() > 0) {
+                // This is not the first number, so place a `,` before the new number.
                 idsNotToGetStringBuilder.append(",");
-                idsNotToGetStringBuilder.append(databaseIdLong);
             }
+
+            // Add the new number to the builder.
+            idsNotToGetStringBuilder.append(databaseIdLong);
         }
 
         // Prepare the SQL statement to select all items except those with the specified IDs.
@@ -436,12 +440,14 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
 
         // Extract the array of IDs not to get to the string builder.
         for (long databaseIdLong : exceptIdLongArray) {
-            if (idsNotToGetStringBuilder.length() == 0) {  // This is the first number, so only add the number.
-                idsNotToGetStringBuilder.append(databaseIdLong);
-            } else {  // This is not the first number, so place a `,` before the new number.
+            // Check to see if there is already a number in the builder.
+            if (idsNotToGetStringBuilder.length() > 0) {
+                // This is not the first number, so place a `,` before the new number.
                 idsNotToGetStringBuilder.append(",");
-                idsNotToGetStringBuilder.append(databaseIdLong);
             }
+
+            // Add the new number to the builder.
+            idsNotToGetStringBuilder.append(databaseIdLong);
         }
 
         // SQL escape the folder name.
@@ -466,12 +472,14 @@ public class BookmarksDatabaseHelper extends SQLiteOpenHelper {
 
         // Extract the array of IDs not to get to the string builder.
         for (long databaseIdLong : exceptIdLongArray) {
-            if (idsNotToGetStringBuilder.length() == 0) {  // This is the first number, so only add the number.
-                idsNotToGetStringBuilder.append(databaseIdLong);
-            } else {  // This is not the first number, so place a `,` before the new number.
+            // Check to see if there is already a number in the builder.
+            if (idsNotToGetStringBuilder.length() > 0) {
+                // This is not the first number, so place a `,` before the new number.
                 idsNotToGetStringBuilder.append(",");
-                idsNotToGetStringBuilder.append(databaseIdLong);
             }
+
+            // Add the new number to the builder.
+            idsNotToGetStringBuilder.append(databaseIdLong);
         }
 
         // SQL escape `folderName`.
diff --git a/app/src/main/java/com/stoutner/privacybrowser/viewmodelfactories/WebViewSourceFactory.kt b/app/src/main/java/com/stoutner/privacybrowser/viewmodelfactories/WebViewSourceFactory.kt
new file mode 100644 (file)
index 0000000..5f8de2a
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.viewmodelfactories
+
+import androidx.annotation.Nullable
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+
+import java.net.Proxy
+import java.util.concurrent.ExecutorService
+
+class WebViewSourceFactory (@Nullable private val urlString: String, private val userAgent: String, private val doNotTrack: Boolean, private val localeString: String, private val proxy: Proxy,
+                            private val executorService: ExecutorService): ViewModelProvider.Factory {
+    // Override the create function in order to add the provided arguments.
+    override fun <T: ViewModel?> create(modelClass: Class<T>): T {
+        // Return a new instance of the model class with the provided arguments.
+        return modelClass.getConstructor(String::class.java, String::class.java, Boolean::class.java, String::class.java, Proxy::class.java, ExecutorService::class.java)
+                .newInstance(urlString, userAgent, doNotTrack, localeString, proxy, executorService)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/viewmodels/WebViewSource.java b/app/src/main/java/com/stoutner/privacybrowser/viewmodels/WebViewSource.java
new file mode 100644 (file)
index 0000000..f1ff7f3
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.viewmodels;
+
+import android.text.SpannableStringBuilder;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import com.stoutner.privacybrowser.backgroundtasks.GetSourceBackgroundTask;
+
+import java.net.Proxy;
+import java.util.concurrent.ExecutorService;
+
+public class WebViewSource extends ViewModel {
+    // Initialize the mutable live data variables.
+    private final MutableLiveData<SpannableStringBuilder[]> mutableLiveDataSourceStringArray = new MutableLiveData<>();
+    private final MutableLiveData<String> mutableLiveDataErrorString = new MutableLiveData<>();
+
+    // Define the class variables.
+    private final String userAgent;
+    private final boolean doNotTrack;
+    private final String localeString;
+    private final Proxy proxy;
+    private final ExecutorService executorService;
+
+    // The public constructor.
+    public WebViewSource(@Nullable String urlString, String userAgent, boolean doNotTrack, String localeString, Proxy proxy, ExecutorService executorService) {
+        // Store the class variables.
+        this.userAgent = userAgent;
+        this.doNotTrack = doNotTrack;
+        this.localeString = localeString;
+        this.proxy = proxy;
+        this.executorService = executorService;
+
+        // Get the source.
+        updateSource(urlString);
+    }
+
+    // The source observer.
+    public LiveData<SpannableStringBuilder[]> observeSource() {
+        // Return the source to the activity.
+        return mutableLiveDataSourceStringArray;
+    }
+
+    // The error observer.
+    public LiveData<String> observeErrors() {
+        // Return any errors to the activity.
+        return mutableLiveDataErrorString;
+    }
+
+    // The interface for returning the error from the background task
+    public void returnError(String errorString) {
+        // Update the mutable live data error string.
+        mutableLiveDataErrorString.postValue(errorString);
+    }
+
+    // The workhorse that gets the source.
+    public void updateSource(String urlString) {
+        // Reset the mutable live data error string.  This prevents the snackbar it from displaying a later if the activity restarts.
+        mutableLiveDataErrorString.postValue("");
+
+        // Instantiate the get source background task class.
+        GetSourceBackgroundTask getSourceBackgroundTask = new GetSourceBackgroundTask();
+
+        // Get the source.
+        executorService.execute(() -> mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, doNotTrack, localeString, proxy, this)));
+    }
+}
\ No newline at end of file
index c3f2fc51e2ee8e341b6cdf7667fffb877d71c186..1ae819c27eace329e4c0b8a7eec111a9a3429d36 100644 (file)
@@ -23,6 +23,7 @@ import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.webkit.HttpAuthHandler;
@@ -44,7 +45,7 @@ import java.util.List;
 
 // NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen).
 public class NestedScrollWebView extends WebView implements NestedScrollingChild2 {
-    // These constants identify the blocklists.
+    // Define the blocklists constants.
     public final static int BLOCKED_REQUESTS = 0;
     public final static int EASYLIST = 1;
     public final static int EASYPRIVACY = 2;
@@ -54,6 +55,38 @@ public class NestedScrollWebView extends WebView implements NestedScrollingChild
     public final static int ULTRAPRIVACY = 6;
     public final static int THIRD_PARTY_REQUESTS = 7;
 
+    // Define the saved state constants.
+    private final String DOMAIN_SETTINGS_APPLIED = "domain_settings_applied";
+    private final String DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_id";
+    private final String CURRENT_URl = "current_url";
+    private final String CURRENT_DOMAIN_NAME = "current_domain_name";
+    private final String ACCEPT_FIRST_PARTY_COOKIES = "accept_first_party_cookies";
+    private final String EASYLIST_ENABLED = "easylist_enabled";
+    private final String EASYPRIVACY_ENABLED = "easyprivacy_enabled";
+    private final String FANBOYS_ANNOYANCE_LIST_ENABLED = "fanboys_annoyance_list_enabled";
+    private final String FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED = "fanboys_social_blocking_list_enabled";
+    private final String ULTRALIST_ENABLED = "ultralist_enabled";
+    private final String ULTRAPRIVACY_ENABLED = "ultraprivacy_enabled";
+    private final String BLOCK_ALL_THIRD_PARTY_REQUESTS = "block_all_third_party_requests";
+    private final String HAS_PINNED_SSL_CERTIFICATE = "has_pinned_ssl_certificate";
+    private final String PINNED_SSL_ISSUED_TO_CNAME = "pinned_ssl_issued_to_cname";
+    private final String PINNED_SSL_ISSUED_TO_ONAME = "pinned_ssl_issued_to_oname";
+    private final String PINNED_SSL_ISSUED_TO_UNAME = "pinned_ssl_issued_to_uname";
+    private final String PINNED_SSL_ISSUED_BY_CNAME = "pinned_ssl_issued_by_cname";
+    private final String PINNED_SSL_ISSUED_BY_ONAME = "pinned_ssl_issued_by_oname";
+    private final String PINNED_SSL_ISSUED_BY_UNAME = "pinned_ssl_issued_by_uname";
+    private final String PINNED_SSL_START_DATE = "pinned_ssl_start_date";
+    private final String PINNED_SSL_END_DATE = "pinned_ssl_end_date";
+    private final String HAS_PINNED_IP_ADDRESSES = "has_pinned_ip_addresses";
+    private final String PINNED_IP_ADDRESSES = "pinned_ip_addresses";
+    private final String IGNORE_PINNED_DOMAIN_INFORMATION = "ignore_pinned_domain_information";
+    private final String SWIPE_TO_REFRESH = "swipe_to_refresh";
+    private final String JAVASCRIPT_ENABLED = "javascript_enabled";
+    private final String DOM_STORAGE_ENABLED = "dom_storage_enabled";
+    private final String USER_AGENT = "user_agent";
+    private final String WIDE_VIEWPORT = "wide_viewport";
+    private final String FONT_SIZE = "font_size";
+
     // Keep a copy of the WebView fragment ID.
     private long webViewFragmentId;
 
@@ -71,7 +104,7 @@ public class NestedScrollWebView extends WebView implements NestedScrollingChild
     // Keep track of when the domain name changes so that domain settings can be reapplied.  This should never be null.
     private String currentDomainName = "";
 
-    // Track the status of first-party cookies.
+    // Track the status of first-party cookies.  This is necessary because first-party cookie status is app wide instead of WebView specific.
     private boolean acceptFirstPartyCookies;
 
     // Track the resource requests.
@@ -740,6 +773,109 @@ public class NestedScrollWebView extends WebView implements NestedScrollingChild
         return motionEventHandled;
     }
 
+    public Bundle saveNestedScrollWebViewState() {
+        // Create a saved state bundle.
+        Bundle savedState = new Bundle();
+
+        // Initialize the long date variables.
+        long pinnedSslStartDateLong = 0;
+        long pinnedSslEndDateLong = 0;
+
+        // Convert the dates to longs.
+        if (pinnedSslStartDate != null) {
+            pinnedSslStartDateLong = pinnedSslStartDate.getTime();
+        }
+
+        if (pinnedSslEndDate != null) {
+            pinnedSslEndDateLong = pinnedSslEndDate.getTime();
+        }
+
+        // Populate the saved state bundle.
+        savedState.putBoolean(DOMAIN_SETTINGS_APPLIED, domainSettingsApplied);
+        savedState.putInt(DOMAIN_SETTINGS_DATABASE_ID, domainSettingsDatabaseId);
+        savedState.putString(CURRENT_URl, currentUrl);
+        savedState.putString(CURRENT_DOMAIN_NAME, currentDomainName);
+        savedState.putBoolean(ACCEPT_FIRST_PARTY_COOKIES, acceptFirstPartyCookies);
+        savedState.putBoolean(EASYLIST_ENABLED, easyListEnabled);
+        savedState.putBoolean(EASYPRIVACY_ENABLED, easyPrivacyEnabled);
+        savedState.putBoolean(FANBOYS_ANNOYANCE_LIST_ENABLED, fanboysAnnoyanceListEnabled);
+        savedState.putBoolean(FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED, fanboysSocialBlockingListEnabled);
+        savedState.putBoolean(ULTRALIST_ENABLED, ultraListEnabled);
+        savedState.putBoolean(ULTRAPRIVACY_ENABLED, ultraPrivacyEnabled);
+        savedState.putBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, blockAllThirdPartyRequests);
+        savedState.putBoolean(HAS_PINNED_SSL_CERTIFICATE, hasPinnedSslCertificate);
+        savedState.putString(PINNED_SSL_ISSUED_TO_CNAME, pinnedSslIssuedToCName);
+        savedState.putString(PINNED_SSL_ISSUED_TO_ONAME, pinnedSslIssuedToOName);
+        savedState.putString(PINNED_SSL_ISSUED_TO_UNAME, pinnedSslIssuedToUName);
+        savedState.putString(PINNED_SSL_ISSUED_BY_CNAME, pinnedSslIssuedByCName);
+        savedState.putString(PINNED_SSL_ISSUED_BY_ONAME, pinnedSslIssuedByOName);
+        savedState.putString(PINNED_SSL_ISSUED_BY_UNAME, pinnedSslIssuedByUName);
+        savedState.putLong(PINNED_SSL_START_DATE, pinnedSslStartDateLong);
+        savedState.putLong(PINNED_SSL_END_DATE, pinnedSslEndDateLong);
+        savedState.putBoolean(HAS_PINNED_IP_ADDRESSES, hasPinnedIpAddresses);
+        savedState.putString(PINNED_IP_ADDRESSES, pinnedIpAddresses);
+        savedState.putBoolean(IGNORE_PINNED_DOMAIN_INFORMATION, ignorePinnedDomainInformation);
+        savedState.putBoolean(SWIPE_TO_REFRESH, swipeToRefresh);
+        savedState.putBoolean(JAVASCRIPT_ENABLED, this.getSettings().getJavaScriptEnabled());
+        savedState.putBoolean(DOM_STORAGE_ENABLED, this.getSettings().getDomStorageEnabled());
+        savedState.putString(USER_AGENT, this.getSettings().getUserAgentString());
+        savedState.putBoolean(WIDE_VIEWPORT, this.getSettings().getUseWideViewPort());
+        savedState.putInt(FONT_SIZE, this.getSettings().getTextZoom());
+
+        // Return the saved state bundle.
+        return savedState;
+    }
+
+    public void restoreNestedScrollWebViewState(Bundle savedState) {
+        // Restore the class variables.
+        domainSettingsApplied = savedState.getBoolean(DOMAIN_SETTINGS_APPLIED);
+        domainSettingsDatabaseId = savedState.getInt(DOMAIN_SETTINGS_DATABASE_ID);
+        currentUrl = savedState.getString(CURRENT_URl);
+        currentDomainName = savedState.getString(CURRENT_DOMAIN_NAME);
+        acceptFirstPartyCookies = savedState.getBoolean(ACCEPT_FIRST_PARTY_COOKIES);
+        easyListEnabled = savedState.getBoolean(EASYLIST_ENABLED);
+        easyPrivacyEnabled = savedState.getBoolean(EASYPRIVACY_ENABLED);
+        fanboysAnnoyanceListEnabled = savedState.getBoolean(FANBOYS_ANNOYANCE_LIST_ENABLED);
+        fanboysSocialBlockingListEnabled = savedState.getBoolean(FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED);
+        ultraListEnabled = savedState.getBoolean(ULTRALIST_ENABLED);
+        ultraPrivacyEnabled = savedState.getBoolean(ULTRAPRIVACY_ENABLED);
+        blockAllThirdPartyRequests = savedState.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS);
+        hasPinnedSslCertificate = savedState.getBoolean(HAS_PINNED_SSL_CERTIFICATE);
+        pinnedSslIssuedToCName = savedState.getString(PINNED_SSL_ISSUED_TO_CNAME);
+        pinnedSslIssuedToOName = savedState.getString(PINNED_SSL_ISSUED_TO_ONAME);
+        pinnedSslIssuedToUName = savedState.getString(PINNED_SSL_ISSUED_TO_UNAME);
+        pinnedSslIssuedByCName = savedState.getString(PINNED_SSL_ISSUED_BY_CNAME);
+        pinnedSslIssuedByOName = savedState.getString(PINNED_SSL_ISSUED_BY_ONAME);
+        pinnedSslIssuedByUName = savedState.getString(PINNED_SSL_ISSUED_BY_UNAME);
+        hasPinnedIpAddresses = savedState.getBoolean(HAS_PINNED_IP_ADDRESSES);
+        pinnedIpAddresses = savedState.getString(PINNED_IP_ADDRESSES);
+        ignorePinnedDomainInformation = savedState.getBoolean(IGNORE_PINNED_DOMAIN_INFORMATION);
+        swipeToRefresh = savedState.getBoolean(SWIPE_TO_REFRESH);
+        this.getSettings().setJavaScriptEnabled(savedState.getBoolean(JAVASCRIPT_ENABLED));
+        this.getSettings().setDomStorageEnabled(savedState.getBoolean(DOM_STORAGE_ENABLED));
+        this.getSettings().setUserAgentString(savedState.getString(USER_AGENT));
+        this.getSettings().setUseWideViewPort(savedState.getBoolean(WIDE_VIEWPORT));
+        this.getSettings().setTextZoom(savedState.getInt(FONT_SIZE));
+
+        // Get the date longs.
+        long pinnedSslStartDateLong = savedState.getLong(PINNED_SSL_START_DATE);
+        long pinnedSslEndDateLong = savedState.getLong(PINNED_SSL_END_DATE);
+
+        // Set the pinned SSL start date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0.
+        if (pinnedSslStartDateLong == 0) {
+            pinnedSslStartDate = null;
+        } else {
+            pinnedSslStartDate = new Date(pinnedSslStartDateLong);
+        }
+
+        // Set the Pinned SSL end date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0.
+        if (pinnedSslEndDateLong == 0) {
+            pinnedSslEndDate = null;
+        } else {
+            pinnedSslEndDate = new Date(pinnedSslEndDateLong);
+        }
+    }
+
     // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
     @Override
     public boolean performClick() {
diff --git a/app/src/main/res/color/primary_text_color_selector_dark.xml b/app/src/main/res/color/primary_text_color_selector_dark.xml
deleted file mode 100644 (file)
index d4cd7f8..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2016-2017 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
-  Privacy Browser is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  Privacy Browser is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<!-- Change the dark theme enabled text from white to gray. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <!-- `#4DFFFFFF` comes from the built-in `@color/primary_text_disabled_material_dark`. -->
-    <item android:state_enabled="false" android:color="#4DFFFFFF" />
-    <item android:color="@color/gray_300" />
-</selector>
\ No newline at end of file
diff --git a/app/src/main/res/color/primary_text_color_selector_light.xml b/app/src/main/res/color/primary_text_color_selector_light.xml
deleted file mode 100644 (file)
index e5a63d1..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2016-2017 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
-  Privacy Browser is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  Privacy Browser is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<!-- Change the dark theme enabled text from white to gray.-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item android:state_enabled="false" android:color="@color/gray_400" />
-    <item android:color="@color/black" />
-</selector>
\ No newline at end of file
diff --git a/app/src/main/res/color/spinner_color_selector_dark.xml b/app/src/main/res/color/spinner_color_selector_dark.xml
deleted file mode 100644 (file)
index 0a683db..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
-  Privacy Browser is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  Privacy Browser is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<!-- Change the dark theme enabled text from white to gray. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item android:state_checked="false" android:color="@color/gray_500" />
-    <item android:color="@color/gray_300" />
-</selector>
\ No newline at end of file
diff --git a/app/src/main/res/color/spinner_color_selector_light.xml b/app/src/main/res/color/spinner_color_selector_light.xml
deleted file mode 100644 (file)
index 8920423..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
-  Privacy Browser is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  Privacy Browser is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<!-- Change the dark theme enabled text from white to gray.-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item android:state_checked="false" android:color="@color/gray_600" />
-    <item android:color="@color/black" />
-</selector>
\ No newline at end of file
index 81284a9dad48f84996e5a098205186a442004d8a..df1245452757da20018d9662450ec7b66aeef4c7 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright © 2017,2019 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2017,2019-2020 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -17,7 +17,8 @@
   You should have received a copy of the GNU General Public License
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<!-- `android:choiceMode="singleChoice"` allows a selected domain to be highlighted.-->
+<!-- `android:choiceMode="singleChoice"` allows a selected domain to be highlighted.
+    `android:dividerHeight` must be at least `1dp` or the list view is inconsistent in calculating the scroll position when restarted.-->
 <ListView
     android:id="@+id/domains_listview"
     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -27,4 +28,4 @@
     android:layout_width="match_parent"
     android:choiceMode="singleChoice"
     android:divider="@color/transparent"
-    android:dividerHeight="0dp" />
\ No newline at end of file
+    android:dividerHeight="1dp" />
\ No newline at end of file
index 0c570ee42d303ba6a1bedae37070a6a5c6db0864..d2b5efa724d7369146cd25fdf9cf823a10f3463f 100644 (file)
   You should have received a copy of the GNU General Public License
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<ScrollView
-    android:id="@+id/domain_settings_scrollview"
+<ScrollView android:id="@+id/domain_settings_scrollview"
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
     android:focusable="true"
     android:focusableInTouchMode="true"
-    android:descendantFocusability="beforeDescendants" >
+    android:descendantFocusability="beforeDescendants"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <LinearLayout
         android:layout_height="wrap_content"
@@ -53,7 +53,7 @@
                     android:layout_marginBottom="12dp"
                     android:layout_gravity="bottom"
                     android:src="@drawable/domains_day"
-                    android:tint="?attr/domainSettingsIconTintColor"
+                    app:tint="?attr/domainSettingsIconTintColor"
                     tools:ignore="contentDescription" />
 
                 <!-- `TextInputLayout` makes the `android:hint` float above the `EditText`. -->
@@ -96,7 +96,7 @@
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/javascript_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/first_party_cookies_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:src="@drawable/cookies_enabled"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/third_party_cookies_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/dom_storage_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/form_data_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/easylist_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/easyprivacy_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/fanboys_annoyance_list_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/fanboys_social_blocking_list_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/ultralist_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/ultraprivacy_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
 
-            <Switch
+            <androidx.appcompat.widget.SwitchCompat
                 android:id="@+id/block_all_third_party_requests_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                     android:layout_marginEnd="10dp"
                     android:layout_gravity="center_vertical"
                     android:src="@drawable/user_agent_day"
-                    android:tint="?attr/domainSettingsIconTintColor"
+                    app:tint="?attr/domainSettingsIconTintColor"
                     android:contentDescription="@string/user_agent" />
 
                 <Spinner
                     android:layout_marginEnd="10dp"
                     android:layout_gravity="center_vertical"
                     android:src="@drawable/font_size_day"
-                    android:tint="?attr/domainSettingsIconTintColor"
+                    app:tint="?attr/domainSettingsIconTintColor"
                     android:contentDescription="@string/font_size" />
 
                 <Spinner
                     android:layout_gravity="center_vertical"
                     tools:ignore="contentDescription" />
 
-                <Switch
+                <androidx.appcompat.widget.SwitchCompat
                     android:id="@+id/pinned_ssl_certificate_switch"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent"
                     android:layout_marginStart="8dp"
                     android:text="@string/pinned_ssl_certificate"
                     android:textColor="?android:textColorPrimary"
-                    android:textSize="18sp" />
+                    android:textSize="18sp"
+                    tools:ignore="TooManyViews" />
             </LinearLayout>
 
             <!-- Saved Certificate -->
                     android:layout_gravity="center_vertical"
                     tools:ignore="contentDescription" />
 
-                <Switch
+                <androidx.appcompat.widget.SwitchCompat
                     android:id="@+id/pinned_ip_addresses_switch"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent"
index 28e1155f8e5d8e6ce7fdb884239ef3a15c8e2c17..b1d3e05feae48496a02a27f4418a7e4f800059a5 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2017,2020 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -17,7 +17,7 @@
   You should have received a copy of the GNU General Public License
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<!-- `android:dividerHeight` must be specified with `android:divider` or the height will be `0dp`.  In our case we want the height to be `0dp`. -->
+<!-- `android:dividerHeight` must be at least `1dp` or the list view is inconsistent in calculating the scroll position when restarted. -->
 <ListView
     android:id="@+id/domains_listview"
     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -26,4 +26,4 @@
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:divider="@color/transparent"
-    android:dividerHeight="0dp" />
\ No newline at end of file
+    android:dividerHeight="1dp" />
\ No newline at end of file
index b21d6a64635682c3ba36ae5289461baecc79c17d..a13ef592804eb30ea6f9558750c6a2db7f6e3575 100644 (file)
@@ -48,6 +48,7 @@
             android:layout_width="match_parent" >
 
             <ScrollView
+                android:id="@+id/logcat_scrollview"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent" >
 
index 8467f38c0bf279e2025ff44b98b474bae4579991..6e64b5943d6afef7a6b4574cf76eb5bd7c950c49 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2017-2018 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2017-2018,2020 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -18,7 +18,7 @@
   You should have received a copy of the GNU General Public License
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<!-- A `CheckedTextView` allows the color of the text to be changed when it is selected (checked). -->
+<!-- A checked text view allows the color of the text to be changed when it is selected (checked). -->
 <CheckedTextView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/spinner_item_textview"
@@ -31,4 +31,4 @@
     android:paddingTop="8dp"
     android:paddingBottom="8dp"
     android:textSize="18sp"
-    android:textColor="?attr/spinnerTextColorSelector" />
\ No newline at end of file
+    android:textColor="@color/checked_text_color_selector" />
\ No newline at end of file
index 00824126488fd7410a710f11b69e689e3a455841..526140654544e64ef9856fea3bbcecd54deebb2a 100644 (file)
@@ -71,6 +71,7 @@
             android:layout_width="match_parent">
 
             <ScrollView
+                android:id="@+id/view_source_scrollview"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent" >
 
index 6f3e530c9f4c40c28337515f6d884bfcb6d8ae9b..2cb5d56de19cd3dbbaa5181851b646f1e4ab149c 100644 (file)
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
 
-    <!-- The nested scroll WebView is created with `visibility="invisible"` to prevent a white background splash in night mode.  It is set visible in `onProgressChanged()` in `MainWebViewActivity`. -->
+    <!-- The nested scroll WebView is created with `visibility="invisible"` to prevent a white background splash in night mode because there is a delay in setting the background color.
+        It is set visible in `initializeWebView()` or `onProgressChanged()` in `MainWebViewActivity`. -->
     <com.stoutner.privacybrowser.views.NestedScrollWebView
         android:id="@+id/nestedscroll_webview"
         android:layout_height="match_parent"
         android:layout_width="match_parent"
         android:focusable="true"
         android:focusableInTouchMode="true"
+        android:background="?android:attr/colorBackground"
         android:visibility="invisible" />
 
     <!-- `android:max` changes the maximum `ProgressBar` value from 10000 to 100 to match progress percentage.
index f200580275bed8497d875427173efc426dda8d89..6d04dee725b3e230e0b29aa93a0ef340b0a3ffa7 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -24,7 +24,7 @@
 
     <!-- `android:iconTint` can be used once the minimum API >= 26 instead of including a separate drawable for each theme. -->
     <item
-        android:id="@+id/options_menu_sort"
+        android:id="@+id/sort"
         android:title="@string/sort"
         android:orderInCategory="10"
         android:icon="?attr/sortIcon"
index 4a94aff87991a65157e39ddb9f90fd8d0962e723..7fbe43dc7a3a92fa73bfc5aa8e3392e638082fdb 100644 (file)
         <item>Herunterziehen zum aktualisieren aktiviert</item>
         <item>Herunterziehen zum aktualisieren deaktiviert</item>
     </string-array>
+    <string-array name="webview_theme_array">
+        <item>System-Einstellung</item>
+        <item>Helles WebView-Erscheinungsbild</item>
+        <item>Dunkles WebView-Erscheinungsbild</item>
+    </string-array>
     <string-array name="wide_viewport_array">
         <item>Standardeinstellung</item>
         <item>Breiter Anzeigebereich aktiviert</item>
             <item>Hell</item>
             <item>Dunkel</item>
         </string-array>
+        <string name="webview_theme">WebView-Erscheinungsbild</string>
+        <string-array name="webview_theme_entries">
+            <item>System-Einstellung</item>
+            <item>Hell</item>
+            <item>Dunkel</item>
+        </string-array>
         <string name="wide_viewport_preference">Breiter Anzeigebereich</string>
         <string name="wide_viewport_summary">Wird der breite Anzeigebereich verwendet, werden manche Webseiten eher wie am Desktop (Standrechner, Laptop) angezeigt.</string>
         <string name="display_webpage_images">Webseiten-Bilder anzeigen</string>
index c7f4950df2ae847fc7683119a25e7a33909d9d59..8362e1e460c79f85f9e91eb9e993a56b236a2f4e 100644 (file)
         <item>Deslizar para actualizar habilitado</item>
         <item>Deslizar para actualizar deshabilitado</item>
     </string-array>
+    <string-array name="webview_theme_array">
+        <item>Por defecto del sistema</item>
+        <item>Tema de WebView claro</item>
+        <item>Tema de WebView oscuro</item>
+    </string-array>
     <string-array name="wide_viewport_array">
         <item>Por defecto del sistema</item>
         <item>Vista amplia habilitada</item>
             <item>Claro</item>
             <item>Oscuro</item>
         </string-array>
+        <string name="webview_theme">Tema de WebView</string>
+        <string-array name="webview_theme_entries">
+            <item>Por defecto del sistema</item>
+            <item>Claro</item>
+            <item>Oscuro</item>
+        </string-array>
         <string name="wide_viewport_preference">Vista amplia</string>
         <string name="wide_viewport_summary">El uso de una vista amplia hace que el diseño de algunas páginas web se parezca más al sitio de escritorio.</string>
         <string name="display_webpage_images">Mostrar imágenes de la página web</string>
index 861c4457cd4477c9706cd6aab1919c09eb6ccb7c..acc4eca0a988efb0d46cfd98888ebd4576f04581 100644 (file)
     <string name="domain_deleted">Domaine supprimé</string>
     <string name="domain_name_instructions">Faire précéder par *. pour inclure l\'ensemble des sous-domaines (ex. *.stoutner.com)</string>
     <string-array name="font_size_array">
-        <item>Réglages systèmes</item>
+        <item>Réglages système</item>
         <item>Taille de police personnalisée</item>
     </string-array>
     <string-array name="swipe_to_refresh_array">
-        <item>Réglages systèmes</item>
+        <item>Réglages système</item>
         <item>Glisser pour actualiser activé</item>
         <item>Glisser pour actualiser déactivé</item>
     </string-array>
+    <string-array name="webview_theme_array">
+        <item>Réglages système</item>
+        <item>Thème WebView clair</item>
+        <item>Thème WebView sombre</item>
+    </string-array>
     <string-array name="wide_viewport_array">
-        <item>Réglages systèmes</item>
+        <item>Réglages système</item>
         <item>Mode fenêtre large activé</item>
         <item>Mode fenêtre largé désactivé</item>
     </string-array>
     <string-array name="display_webpage_images_array">
-        <item>Réglages systèmes</item>
+        <item>Réglages système</item>
         <item>Images affichées</item>
         <item>Images masquées</item>
     </string-array>
             pour activer/désactiver les cookies et le stockage DOM.</string>
         <string name="app_theme">Thème de l\'application</string>
         <string-array name="app_theme_entries">
-            <item>Par defaut</item>
+            <item>Par défaut</item>
+            <item>Clair</item>
+            <item>Sombre</item>
+        </string-array>
+        <string name="webview_theme">Thème Android WebView</string>
+        <string-array name="webview_theme_entries">
+            <item>Par défaut</item>
             <item>Clair</item>
             <item>Sombre</item>
         </string-array>
index a87b07c3b8326cea44fc71b9ceec8fa0f94ad42f..349183e2a7c22542cbe53bb31486f53ffa7d140f 100644 (file)
         <item>Swipe per aggiornare abilitato</item>
         <item>Swipe per aggiornare disabilitato</item>
     </string-array>
+    <string-array name="webview_theme_array">
+        <item>Predefinito</item>
+        <item>Tema Chiaro</item>
+        <item>Tema Scuro</item>
+    </string-array>
     <string-array name="wide_viewport_array">
         <item>Impostazioni di default</item>
         <item>Finestra grande abilitata</item>
             <item>Chiaro</item>
             <item>Scuro</item>
         </string-array>
+        <string name="webview_theme">Tema di WebView</string>
+        <string-array name="webview_theme_entries">
+            <item>Predefinito</item>
+            <item>Chiaro</item>
+            <item>Scuro</item>
+        </string-array>
         <string name="wide_viewport_preference">Finestra grande</string>
         <string name="wide_viewport_summary">L\'utilizzo di una finestra grande permette la visualizzazione di alcune pagine web come in modalità desktop.</string>
         <string name="display_webpage_images">Mostra immagini delle pagine web</string>
index 61649bbc38a1694aedf901c226a114c009d4a24a..165463de8de40f3906e8f0eb8f17b6f0173c52d4 100644 (file)
         <item>Потянуть для обновления - включено</item>
         <item>Потянуть для обновления - выключено</item>
     </string-array>
+    <string-array name="webview_theme_array">
+        <item>Настройки по умолчанию</item>
+        <item>Светлая тема WebView</item>
+        <item>Темная тема WebView</item>
+    </string-array>
     <string-array name="wide_viewport_array">
         <item>Настройки по умолчанию</item>
         <item>Широкий вид просмотра включен</item>
         <string name="scroll_app_bar_summary">Прокручивает панель приложения вверху экрана при прокрутке WebView вниз.</string>
         <string name="display_additional_app_bar_icons">Отображать дополнительные значки на панели приложения</string>
         <string name="display_additional_app_bar_icons_summary">Отображать значки на панели приложения для обновления WebView и, при наличии места, для переключения файлов cookie и хранилища DOM</string>
-        <string name="app_theme">Тема</string>
+        <string name="app_theme">Тема приложения</string>
         <string-array name="app_theme_entries">
             <item>По умолчанию</item>
             <item>Светлая</item>
             <item>Темная</item>
         </string-array>
+        <string name="webview_theme">Тема WebView</string>
+        <string-array name="webview_theme_entries">
+            <item>По умолчанию</item>
+            <item>Светлая</item>
+            <item>Темная</item>
+        </string-array>
         <string name="wide_viewport_preference">Широкий вид просмотра</string>
         <string name="wide_viewport_summary">Использование широкого вида просмотра делает стиль некоторых веб-страниц более похожим на сайт для компьютера.</string>
         <string name="display_webpage_images">Показывать изображения веб-страницы</string>
index 7a9e08c6cb678c80398807ffbcffdfd04208c0c6..e82d613526aa5914104a8d6a55fcf7886fc5f523 100644 (file)
@@ -37,7 +37,7 @@
 
         <!-- Tints. -->
         <item name="addTabIconTintColor">@color/gray_700</item>
-        <item name="domainSettingsIconTintColor">@color/blue_700</item>
+        <item name="domainSettingsIconTintColor">@color/blue_800</item>
         <item name="fabIconTintColor">@color/white</item>
         <item name="findOnPageIconTintColor">@color/blue_800</item>
         <item name="navigationIconTintColor">@color/blue_800</item>
index efe68c8c79a7ab7fcd8779cb5937e149cc5fca14..31ccd30184dc8afaa8b650d2bc614517d1a825cd 100644 (file)
@@ -39,7 +39,7 @@
 
         <!-- Tints. -->
         <item name="addTabIconTintColor">@color/gray_700</item>
-        <item name="domainSettingsIconTintColor">@color/blue_700</item>
+        <item name="domainSettingsIconTintColor">@color/blue_800</item>
         <item name="fabIconTintColor">@color/white</item>
         <item name="findOnPageIconTintColor">@color/blue_800</item>
         <item name="navigationIconTintColor">@color/blue_800</item>
index dd911345b6cb086f468382eed65ae3fa05930f97..da413e3d4d1730818f54cdf90c799a6ff1edb186 100644 (file)
@@ -27,7 +27,6 @@
     <attr name="buttonBackgroundColorSelector" format="color" />
     <attr name="buttonTextColorSelector" format="color" />
     <attr name="redTextColor" format="color" />
-    <attr name="spinnerTextColorSelector" format="color" />
 
     <!-- Tint Colors. -->
     <attr name="addTabIconTintColor" format="color" />
index 08fcb8a4229c2f109e613483ef3fe15b0d82c35c..f8676c90151aacf51059fc125bf21ce521edb928 100644 (file)
@@ -35,7 +35,7 @@
 
         <!-- Tints. -->
         <item name="addTabIconTintColor">@color/gray_700</item>
-        <item name="domainSettingsIconTintColor">@color/blue_700</item>
+        <item name="domainSettingsIconTintColor">@color/blue_800</item>
         <item name="fabIconTintColor">@color/white</item>
         <item name="findOnPageIconTintColor">@color/blue_800</item>
         <item name="navigationIconTintColor">@color/blue_800</item>
index 5273a5c75631f793b4061859ba8249ead00ad22e..a2368ae18f3d302029377ad040d182f4fc8ed30a 100644 (file)
@@ -25,7 +25,7 @@ buildscript {
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.0.0'
+        classpath 'com.android.tools.build:gradle:4.0.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
 
         // NOTE: Do not place your application dependencies here; they belong