From: Soren Stoutner Date: Fri, 14 Aug 2020 21:54:30 +0000 (-0700) Subject: Save and restore the app state. https://redmine.stoutner.com/issues/461 X-Git-Tag: v3.5~1 X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff_plain;h=b82022327701273b1b56419e8d6042895c0bc7b9 Save and restore the app state. https://redmine.stoutner.com/issues/461 --- diff --git a/app/build.gradle b/app/build.gradle index 91d353a7..fe01dec6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java index 3a96929f..735d3e39 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java @@ -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 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 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. diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java index c4b97517..17c9e879 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java @@ -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) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java index 6baf5f6e..5d0589fc 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java @@ -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. diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java index cc9363f9..53c1109c 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java @@ -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) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java index de0b873d..7878f985 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java @@ -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 { - // Create a weak reference to the calling activity. - private final WeakReference 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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index c130a267..4280c56a 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -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 savedStateArrayList; + private ArrayList 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 savedStateArrayList = new ArrayList<>(); + ArrayList 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("")) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java index 2adeb318..eb6fb4fe 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java @@ -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 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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java index 51036fbc..e4899ea3 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java @@ -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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.java b/app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.java index d1583c2f..9d4c9191 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.java +++ b/app/src/main/java/com/stoutner/privacybrowser/adapters/AboutPagerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 Soren Stoutner . + * Copyright © 2016-2020 Soren Stoutner . * * This file is part of 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; } diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java b/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java index fccf1883..26d376c2 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java +++ b/app/src/main/java/com/stoutner/privacybrowser/adapters/GuidePagerAdapter.java @@ -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; } diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/WebViewPagerAdapter.java b/app/src/main/java/com/stoutner/privacybrowser/adapters/WebViewPagerAdapter.java index 535512a5..510ac739 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/adapters/WebViewPagerAdapter.java +++ b/app/src/main/java/com/stoutner/privacybrowser/adapters/WebViewPagerAdapter.java @@ -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 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 index 00000000..4722dbfc --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetLogcat.java @@ -0,0 +1,121 @@ +/* + * Copyright © 2020 Soren Stoutner . + * + * This file is part of 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 . + */ + +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 { + // Define the class variables. + private final WeakReference 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 index f5b4e54b..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java +++ /dev/null @@ -1,534 +0,0 @@ -/* - * Copyright © 2017-2020 Soren Stoutner . - * - * This file is part of 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 . - */ - -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 { - // Define weak references to the calling context and activity. - private WeakReference contextWeakReference; - private WeakReference 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 index 00000000..1311497f --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetSourceBackgroundTask.java @@ -0,0 +1,371 @@ +/* + * Copyright © 2017-2020 Soren Stoutner . + * + * This file is part of 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 . + */ + +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}; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt index 4de35431..a3c16452 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt @@ -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(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. + } } } diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java index 4b075d3b..3a68a616 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java @@ -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. diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java index 19b591c7..c35b90b6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java @@ -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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java index 1588ad3c..35d62693 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java @@ -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; } diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java index 2aa974b9..2d22d9c7 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java @@ -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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTabFragment.java index 6a2e82f1..6f3aa0d3 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTabFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTabFragment.java @@ -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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/WebViewTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/WebViewTabFragment.java index 91866a3e..1acf3b54 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/WebViewTabFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/WebViewTabFragment.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Soren Stoutner . + * Copyright © 2019-2020 Soren Stoutner . * * This file is part of 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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java index 984402e8..529a769b 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 Soren Stoutner . + * Copyright © 2016-2020 Soren Stoutner . * * This file is part of 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 index 00000000..5f8de2a2 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/viewmodelfactories/WebViewSourceFactory.kt @@ -0,0 +1,37 @@ +/* + * Copyright © 2020 Soren Stoutner . + * + * This file is part of 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 . + */ + +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 create(modelClass: Class): 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 index 00000000..f1ff7f3c --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/viewmodels/WebViewSource.java @@ -0,0 +1,88 @@ +/* + * Copyright © 2020 Soren Stoutner . + * + * This file is part of 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 . + */ + +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 mutableLiveDataSourceStringArray = new MutableLiveData<>(); + private final MutableLiveData 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 observeSource() { + // Return the source to the activity. + return mutableLiveDataSourceStringArray; + } + + // The error observer. + public LiveData 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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java b/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java index c3f2fc51..1ae819c2 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java +++ b/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java @@ -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 index d4cd7f84..00000000 --- a/app/src/main/res/color/primary_text_color_selector_dark.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - \ 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 index e5a63d1c..00000000 --- a/app/src/main/res/color/primary_text_color_selector_light.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ 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 index 0a683dbc..00000000 --- a/app/src/main/res/color/spinner_color_selector_dark.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ 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 index 89204238..00000000 --- a/app/src/main/res/color/spinner_color_selector_light.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-w900dp/domains_list_fragment.xml b/app/src/main/res/layout-w900dp/domains_list_fragment.xml index 81284a9d..df124545 100644 --- a/app/src/main/res/layout-w900dp/domains_list_fragment.xml +++ b/app/src/main/res/layout-w900dp/domains_list_fragment.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file + android:dividerHeight="1dp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/domain_settings_fragment.xml b/app/src/main/res/layout/domain_settings_fragment.xml index 0c570ee4..d2b5efa7 100644 --- a/app/src/main/res/layout/domain_settings_fragment.xml +++ b/app/src/main/res/layout/domain_settings_fragment.xml @@ -18,15 +18,15 @@ You should have received a copy of the GNU General Public License along with Privacy Browser. If not, see . --> - + android:descendantFocusability="beforeDescendants" + xmlns:app="http://schemas.android.com/apk/res-auto"> @@ -96,7 +96,7 @@ android:layout_gravity="center_vertical" tools:ignore="contentDescription" /> - - - - - - - - - - - - - + android:textSize="18sp" + tools:ignore="TooManyViews" /> @@ -927,7 +928,7 @@ android:layout_gravity="center_vertical" tools:ignore="contentDescription" /> - - + \ No newline at end of file + android:dividerHeight="1dp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/logcat_coordinatorlayout.xml b/app/src/main/res/layout/logcat_coordinatorlayout.xml index b21d6a64..a13ef592 100644 --- a/app/src/main/res/layout/logcat_coordinatorlayout.xml +++ b/app/src/main/res/layout/logcat_coordinatorlayout.xml @@ -48,6 +48,7 @@ android:layout_width="match_parent" > diff --git a/app/src/main/res/layout/spinner_dropdown_items.xml b/app/src/main/res/layout/spinner_dropdown_items.xml index 8467f38c..6e64b594 100644 --- a/app/src/main/res/layout/spinner_dropdown_items.xml +++ b/app/src/main/res/layout/spinner_dropdown_items.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file + android:textColor="@color/checked_text_color_selector" /> \ No newline at end of file diff --git a/app/src/main/res/layout/view_source_coordinatorlayout.xml b/app/src/main/res/layout/view_source_coordinatorlayout.xml index 00824126..52614065 100644 --- a/app/src/main/res/layout/view_source_coordinatorlayout.xml +++ b/app/src/main/res/layout/view_source_coordinatorlayout.xml @@ -71,6 +71,7 @@ android:layout_width="match_parent"> diff --git a/app/src/main/res/layout/webview_framelayout.xml b/app/src/main/res/layout/webview_framelayout.xml index 6f3e530c..2cb5d56d 100644 --- a/app/src/main/res/layout/webview_framelayout.xml +++ b/app/src/main/res/layout/webview_framelayout.xml @@ -25,13 +25,15 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - + Herunterziehen zum aktualisieren aktiviert Herunterziehen zum aktualisieren deaktiviert + + System-Einstellung + Helles WebView-Erscheinungsbild + Dunkles WebView-Erscheinungsbild + Standardeinstellung Breiter Anzeigebereich aktiviert @@ -587,6 +592,12 @@ Hell Dunkel + WebView-Erscheinungsbild + + System-Einstellung + Hell + Dunkel + Breiter Anzeigebereich Wird der breite Anzeigebereich verwendet, werden manche Webseiten eher wie am Desktop (Standrechner, Laptop) angezeigt. Webseiten-Bilder anzeigen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c7f4950d..8362e1e4 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -324,6 +324,11 @@ Deslizar para actualizar habilitado Deslizar para actualizar deshabilitado + + Por defecto del sistema + Tema de WebView claro + Tema de WebView oscuro + Por defecto del sistema Vista amplia habilitada @@ -583,6 +588,12 @@ Claro Oscuro + Tema de WebView + + Por defecto del sistema + Claro + Oscuro + Vista amplia El uso de una vista amplia hace que el diseño de algunas páginas web se parezca más al sitio de escritorio. Mostrar imágenes de la página web diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 861c4457..acc4eca0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -316,21 +316,26 @@ Domaine supprimé Faire précéder par *. pour inclure l\'ensemble des sous-domaines (ex. *.stoutner.com) - Réglages systèmes + Réglages système Taille de police personnalisée - Réglages systèmes + Réglages système Glisser pour actualiser activé Glisser pour actualiser déactivé + + Réglages système + Thème WebView clair + Thème WebView sombre + - Réglages systèmes + Réglages système Mode fenêtre large activé Mode fenêtre largé désactivé - Réglages systèmes + Réglages système Images affichées Images masquées @@ -581,7 +586,13 @@ pour activer/désactiver les cookies et le stockage DOM. Thème de l\'application - Par defaut + Par défaut + Clair + Sombre + + Thème Android WebView + + Par défaut Clair Sombre diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a87b07c3..349183e2 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -323,6 +323,11 @@ Swipe per aggiornare abilitato Swipe per aggiornare disabilitato + + Predefinito + Tema Chiaro + Tema Scuro + Impostazioni di default Finestra grande abilitata @@ -583,6 +588,12 @@ Chiaro Scuro + Tema di WebView + + Predefinito + Chiaro + Scuro + Finestra grande L\'utilizzo di una finestra grande permette la visualizzazione di alcune pagine web come in modalità desktop. Mostra immagini delle pagine web diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 61649bbc..165463de 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -320,6 +320,11 @@ Потянуть для обновления - включено Потянуть для обновления - выключено + + Настройки по умолчанию + Светлая тема WebView + Темная тема WebView + Настройки по умолчанию Широкий вид просмотра включен @@ -570,12 +575,18 @@ Прокручивает панель приложения вверху экрана при прокрутке WebView вниз. Отображать дополнительные значки на панели приложения Отображать значки на панели приложения для обновления WebView и, при наличии места, для переключения файлов cookie и хранилища DOM - Тема + Тема приложения По умолчанию Светлая Темная + Тема WebView + + По умолчанию + Светлая + Темная + Широкий вид просмотра Использование широкого вида просмотра делает стиль некоторых веб-страниц более похожим на сайт для компьютера. Показывать изображения веб-страницы diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml index 7a9e08c6..e82d6135 100644 --- a/app/src/main/res/values-v23/styles.xml +++ b/app/src/main/res/values-v23/styles.xml @@ -37,7 +37,7 @@ @color/gray_700 - @color/blue_700 + @color/blue_800 @color/white @color/blue_800 @color/blue_800 diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml index efe68c8c..31ccd301 100644 --- a/app/src/main/res/values-v27/styles.xml +++ b/app/src/main/res/values-v27/styles.xml @@ -39,7 +39,7 @@ @color/gray_700 - @color/blue_700 + @color/blue_800 @color/white @color/blue_800 @color/blue_800 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index dd911345..da413e3d 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -27,7 +27,6 @@ - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 08fcb8a4..f8676c90 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -35,7 +35,7 @@ @color/gray_700 - @color/blue_700 + @color/blue_800 @color/white @color/blue_800 @color/blue_800 diff --git a/build.gradle b/build.gradle index 5273a5c7..a2368ae1 100644 --- a/build.gradle +++ b/build.gradle @@ -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