]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java
Display memory usage information in About > Version. https://redmine.stoutner.com...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / fragments / AboutTabFragment.java
index fbc487e9176c8ff75daaca08376398bfbe4f2ef9..9d8a2f1c43ebca2c7926b7f3a01dd62753217977 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
 package com.stoutner.privacybrowser.fragments;
 
 import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
+import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.Fragment;
+import android.os.Handler;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
@@ -35,9 +39,12 @@ import android.view.ViewGroup;
 import android.webkit.WebView;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.webkit.WebViewCompat;
+
 import com.stoutner.privacybrowser.BuildConfig;
 import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.activities.MainWebViewActivity;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
@@ -47,22 +54,55 @@ import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.text.DateFormat;
+import java.text.NumberFormat;
 import java.util.Date;
 
 public class AboutTabFragment extends Fragment {
-    private int tabNumber;
+    // Declare the class constants.
+    final static String TAB_NUMBER = "tab_number";
+    final static String BLOCKLIST_VERSIONS = "blocklist_versions";
+    final long MEBIBYTE = 1048576;
 
-    // Store the tab number in the arguments bundle.
-    public static AboutTabFragment createTab(int tab) {
+    // Declare the class variables.
+    private boolean updateMemoryUsageBoolean = true;
+    private int tabNumber;
+    private String[] blocklistVersions;
+    private View tabLayout;
+    private String appConsumedMemoryLabel;
+    private String appAvailableMemoryLabel;
+    private String appTotalMemoryLabel;
+    private String appMaximumMemoryLabel;
+    private String systemConsumedMemoryLabel;
+    private String systemAvailableMemoryLabel;
+    private String systemTotalMemoryLabel;
+    private Runtime runtime;
+    private ActivityManager activityManager;
+    private ActivityManager.MemoryInfo memoryInfo;
+    private NumberFormat numberFormat;
+    private ForegroundColorSpan blueColorSpan;
+
+    // Declare the class views.
+    private TextView appConsumedMemoryTextView;
+    private TextView appAvailableMemoryTextView;
+    private TextView appTotalMemoryTextView;
+    private TextView appMaximumMemoryTextView;
+    private TextView systemConsumedMemoryTextView;
+    private TextView systemAvailableMemoryTextView;
+    private TextView systemTotalMemoryTextView;
+
+    public static AboutTabFragment createTab(int tabNumber, String[] blocklistVersions) {
         // Create a bundle.
-        Bundle bundle = new Bundle();
+        Bundle argumentsBundle = new Bundle();
 
         // Store the tab number in the bundle.
-        bundle.putInt("Tab", tab);
+        argumentsBundle.putInt(TAB_NUMBER, tabNumber);
+        argumentsBundle.putStringArray(BLOCKLIST_VERSIONS, blocklistVersions);
 
-        // Add the bundle to the fragment.
+        // Create a new instance of the tab fragment.
         AboutTabFragment aboutTabFragment = new AboutTabFragment();
-        aboutTabFragment.setArguments(bundle);
+
+        // Add the arguments bundle to the fragment.
+        aboutTabFragment.setArguments(argumentsBundle);
 
         // Return the new fragment.
         return aboutTabFragment;
@@ -73,50 +113,70 @@ public class AboutTabFragment 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 incorrect lint warning below that arguments might be null.
+        assert arguments != null;
 
-        // Store the tab number in a class variable.
-        tabNumber = getArguments().getInt("Tab");
+        // Store the arguments in class variables.
+        tabNumber = getArguments().getInt(TAB_NUMBER);
+        blocklistVersions = getArguments().getStringArray(BLOCKLIST_VERSIONS);
     }
 
     @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        View tabLayout;
+    public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
+        // Get a handle for the context and assert that it isn't null.
+        Context context = getContext();
+        assert context != null;
+
+        // Get the current theme status.
+        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
         // Load the tabs.  Tab numbers start at 0.
         if (tabNumber == 0) {  // Load the about tab.
             // 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.
-            tabLayout = inflater.inflate(R.layout.about_tab_version, container, false);
-
-            // Get handles for the `TextViews`.
-            TextView versionNumberTextView = tabLayout.findViewById(R.id.about_version_number);
-            TextView versionBrandTextView = tabLayout.findViewById(R.id.about_version_brand);
-            TextView versionManufacturerTextView = tabLayout.findViewById(R.id.about_version_manufacturer);
-            TextView versionModelTextView = tabLayout.findViewById(R.id.about_version_model);
-            TextView versionDeviceTextView = tabLayout.findViewById(R.id.about_version_device);
-            TextView versionBootloaderTextView = tabLayout.findViewById(R.id.about_version_bootloader);
-            TextView versionRadioTextView = tabLayout.findViewById(R.id.about_version_radio);
-            TextView versionAndroidTextView = tabLayout.findViewById(R.id.about_version_android);
-            TextView versionSecurityPatchTextView = tabLayout.findViewById(R.id.about_version_securitypatch);
-            TextView versionBuildTextView = tabLayout.findViewById(R.id.about_version_build);
-            TextView versionWebKitTextView = tabLayout.findViewById(R.id.about_version_webkit);
-            TextView versionChromeTextView = tabLayout.findViewById(R.id.about_version_chrome);
-            TextView versionOrbotTextView = tabLayout.findViewById(R.id.about_version_orbot);
-            TextView versionEasyListTextView = tabLayout.findViewById(R.id.about_version_easylist);
-            TextView versionEasyPrivacyTextView = tabLayout.findViewById(R.id.about_version_easyprivacy);
-            TextView versionFanboyAnnoyanceTextView = tabLayout.findViewById(R.id.about_version_fanboy_annoyance);
-            TextView versionFanboySocialTextView = tabLayout.findViewById(R.id.about_version_fanboy_social);
-            TextView certificateIssuerDNTextView = tabLayout.findViewById(R.id.about_version_certificate_issuer_dn);
-            TextView certificateSubjectDNTextView = tabLayout.findViewById(R.id.about_version_certificate_subject_dn);
-            TextView certificateStartDateTextView = tabLayout.findViewById(R.id.about_version_certificate_start_date);
-            TextView certificateEndDateTextView = tabLayout.findViewById(R.id.about_version_certificate_end_date);
-            TextView certificateVersionTextView = tabLayout.findViewById(R.id.about_version_certificate_version);
-            TextView certificateSerialNumberTextView = tabLayout.findViewById(R.id.about_version_certificate_serial_number);
-            TextView certificateSignatureAlgorithmTextView = tabLayout.findViewById(R.id.about_version_certificate_signature_algorithm);
+            tabLayout = layoutInflater.inflate(R.layout.about_tab_version, container, false);
+
+            // Get handles for the text views.
+            TextView versionTextView = tabLayout.findViewById(R.id.version);
+            TextView brandTextView = tabLayout.findViewById(R.id.brand);
+            TextView manufacturerTextView = tabLayout.findViewById(R.id.manufacturer);
+            TextView modelTextView = tabLayout.findViewById(R.id.model);
+            TextView deviceTextView = tabLayout.findViewById(R.id.device);
+            TextView bootloaderTextView = tabLayout.findViewById(R.id.bootloader);
+            TextView radioTextView = tabLayout.findViewById(R.id.radio);
+            TextView androidTextView = tabLayout.findViewById(R.id.android);
+            TextView securityPatchTextView = tabLayout.findViewById(R.id.security_patch);
+            TextView buildTextView = tabLayout.findViewById(R.id.build);
+            TextView webViewProviderTextView = tabLayout.findViewById(R.id.webview_provider);
+            TextView webViewVersionTextView = tabLayout.findViewById(R.id.webview_version);
+            TextView orbotTextView = tabLayout.findViewById(R.id.orbot);
+            TextView i2pTextView = tabLayout.findViewById(R.id.i2p);
+            TextView openKeychainTextView = tabLayout.findViewById(R.id.open_keychain);
+            appConsumedMemoryTextView = tabLayout.findViewById(R.id.app_consumed_memory);
+            appAvailableMemoryTextView = tabLayout.findViewById(R.id.app_available_memory);
+            appTotalMemoryTextView = tabLayout.findViewById(R.id.app_total_memory);
+            appMaximumMemoryTextView = tabLayout.findViewById(R.id.app_maximum_memory);
+            systemConsumedMemoryTextView = tabLayout.findViewById(R.id.system_consumed_memory);
+            systemAvailableMemoryTextView = tabLayout.findViewById(R.id.system_available_memory);
+            systemTotalMemoryTextView = tabLayout.findViewById(R.id.system_total_memory);
+            TextView easyListTextView = tabLayout.findViewById(R.id.easylist);
+            TextView easyPrivacyTextView = tabLayout.findViewById(R.id.easyprivacy);
+            TextView fanboyAnnoyanceTextView = tabLayout.findViewById(R.id.fanboy_annoyance);
+            TextView fanboySocialTextView = tabLayout.findViewById(R.id.fanboy_social);
+            TextView ultraListTextView = tabLayout.findViewById(R.id.ultralist);
+            TextView ultraPrivacyTextView = tabLayout.findViewById(R.id.ultraprivacy);
+            TextView certificateIssuerDNTextView = tabLayout.findViewById(R.id.certificate_issuer_dn);
+            TextView certificateSubjectDNTextView = tabLayout.findViewById(R.id.certificate_subject_dn);
+            TextView certificateStartDateTextView = tabLayout.findViewById(R.id.certificate_start_date);
+            TextView certificateEndDateTextView = tabLayout.findViewById(R.id.certificate_end_date);
+            TextView certificateVersionTextView = tabLayout.findViewById(R.id.certificate_version);
+            TextView certificateSerialNumberTextView = tabLayout.findViewById(R.id.certificate_serial_number);
+            TextView certificateSignatureAlgorithmTextView = tabLayout.findViewById(R.id.certificate_signature_algorithm);
 
             // Setup the labels.
-            String version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + Integer.toString(BuildConfig.VERSION_CODE) + ")";
+            String version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + BuildConfig.VERSION_CODE + ")";
             String brandLabel = getString(R.string.brand) + "  ";
             String manufacturerLabel = getString(R.string.manufacturer) + "  ";
             String modelLabel = getString(R.string.model) + "  ";
@@ -124,12 +184,20 @@ public class AboutTabFragment extends Fragment {
             String bootloaderLabel = getString(R.string.bootloader) + "  ";
             String androidLabel = getString(R.string.android) + "  ";
             String buildLabel = getString(R.string.build) + "  ";
-            String webKitLabel = getString(R.string.webkit) + "  ";
-            String chromeLabel = getString(R.string.chrome) + "  ";
+            String webViewVersionLabel = getString(R.string.webview_version) + "  ";
+            appConsumedMemoryLabel = getString(R.string.app_consumed_memory) + "  ";
+            appAvailableMemoryLabel = getString(R.string.app_available_memory) + "  ";
+            appTotalMemoryLabel = getString(R.string.app_total_memory) + "  ";
+            appMaximumMemoryLabel = getString(R.string.app_maximum_memory) + "  ";
+            systemConsumedMemoryLabel = getString(R.string.system_consumed_memory) + "  ";
+            systemAvailableMemoryLabel = getString(R.string.system_available_memory) + "  ";
+            systemTotalMemoryLabel = getString(R.string.system_total_memory) + "  ";
             String easyListLabel = getString(R.string.easylist_label) + "  ";
             String easyPrivacyLabel = getString(R.string.easyprivacy_label) + "  ";
             String fanboyAnnoyanceLabel = getString(R.string.fanboy_annoyance_label) + "  ";
             String fanboySocialLabel = getString(R.string.fanboy_social_label) + "  ";
+            String ultraListLabel = getString(R.string.ultralist_label) + "  ";
+            String ultraPrivacyLabel = getString(R.string.ultraprivacy_label) + "  ";
             String issuerDNLabel = getString(R.string.issuer_dn) + "  ";
             String subjectDNLabel = getString(R.string.subject_dn) + "  ";
             String startDateLabel = getString(R.string.start_date) + "  ";
@@ -138,8 +206,9 @@ public class AboutTabFragment extends Fragment {
             String serialNumberLabel = getString(R.string.serial_number) + "  ";
             String signatureAlgorithmLabel = getString(R.string.signature_algorithm) + "  ";
 
-            // `webViewLayout` is only used to get the default user agent from `bare_webview`.  It is not used to render content on the screen.
-            View webViewLayout = inflater.inflate(R.layout.bare_webview, container, false);
+            // The WebView layout is only used to get the default user agent from `bare_webview`.  It is not used to render content on the screen.
+            // Once the minimum API >= 26 this can be accomplished with the WebView package info.
+            View webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false);
             WebView tabLayoutWebView = webViewLayout.findViewById(R.id.bare_webview);
             String userAgentString =  tabLayoutWebView.getSettings().getUserAgentString();
 
@@ -150,26 +219,39 @@ public class AboutTabFragment extends Fragment {
             String device = Build.DEVICE;
             String bootloader = Build.BOOTLOADER;
             String radio = Build.getRadioVersion();
-            String android = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Integer.toString(Build.VERSION.SDK_INT) + ")";
+            String android = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Build.VERSION.SDK_INT + ")";
             String build = Build.DISPLAY;
-            // Select the substring that begins after "Safari/" and goes to the end of the string.
-            String webKit = userAgentString.substring(userAgentString.indexOf("Safari/") + 7);
-            // Select the substring that begins after "Chrome/" and goes until the next " ".
-            String chrome = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")));
+            // Select the substring that begins after `Chrome/` and goes until the next ` `.
+            String webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")));
 
             // Get the Orbot version name if Orbot is installed.
             String orbot;
             try {
-                // Remove the lint warning that `getContext()` might be null.
-                assert getContext() != null;
-
                 // Store the version name.
-                orbot = getContext().getPackageManager().getPackageInfo("org.torproject.android", PackageManager.GET_CONFIGURATIONS).versionName;
-            } catch (PackageManager.NameNotFoundException e) {  // Orbot is not installed.
+                orbot = context.getPackageManager().getPackageInfo("org.torproject.android", 0).versionName;
+            } catch (PackageManager.NameNotFoundException exception) {  // Orbot is not installed.
                 orbot = "";
             }
 
-            // Create a `SpannableStringBuilder` for the hardware and software `TextViews` that needs multiple colors of text.
+            // Get the I2P version name if I2P is installed.
+            String i2p;
+            try {
+                // Store the version name.
+                i2p = context.getPackageManager().getPackageInfo("net.i2p.android.router", 0).versionName;
+            } catch (PackageManager.NameNotFoundException exception) {  // I2P is not installed.
+                i2p = "";
+            }
+
+            // Get the OpenKeychain version name if it is installed.
+            String openKeychain;
+            try {
+                // Store the version name.
+                openKeychain = context.getPackageManager().getPackageInfo("org.sufficientlysecure.keychain", 0).versionName;
+            } catch (PackageManager.NameNotFoundException exception) {  // OpenKeychain is not installed.
+                openKeychain = "";
+            }
+
+            // Create a spannable string builder for the hardware and software text views that needs multiple colors of text.
             SpannableStringBuilder brandStringBuilder = new SpannableStringBuilder(brandLabel + brand);
             SpannableStringBuilder manufacturerStringBuilder = new SpannableStringBuilder(manufacturerLabel + manufacturer);
             SpannableStringBuilder modelStringBuilder = new SpannableStringBuilder(modelLabel + model);
@@ -177,23 +259,19 @@ public class AboutTabFragment extends Fragment {
             SpannableStringBuilder bootloaderStringBuilder = new SpannableStringBuilder(bootloaderLabel + bootloader);
             SpannableStringBuilder androidStringBuilder = new SpannableStringBuilder(androidLabel + android);
             SpannableStringBuilder buildStringBuilder = new SpannableStringBuilder(buildLabel + build);
-            SpannableStringBuilder webKitStringBuilder = new SpannableStringBuilder(webKitLabel + webKit);
-            SpannableStringBuilder chromeStringBuilder = new SpannableStringBuilder(chromeLabel + chrome);
-            SpannableStringBuilder easyListStringBuilder = new SpannableStringBuilder(easyListLabel + MainWebViewActivity.easyListVersion);
-            SpannableStringBuilder easyPrivacyStringBuilder = new SpannableStringBuilder(easyPrivacyLabel + MainWebViewActivity.easyPrivacyVersion);
-            SpannableStringBuilder fanboyAnnoyanceStringBuilder = new SpannableStringBuilder(fanboyAnnoyanceLabel + MainWebViewActivity.fanboysAnnoyanceVersion);
-            SpannableStringBuilder fanboySocialStringBuilder = new SpannableStringBuilder(fanboySocialLabel + MainWebViewActivity.fanboysSocialVersion);
-
-            // Create the `blueColorSpan` variable.
-            ForegroundColorSpan blueColorSpan;
-
-            // Set `blueColorSpan` according to the theme.  We have to use the deprecated `getColor()` until API >= 23.
-            if (MainWebViewActivity.darkTheme) {
-                //noinspection deprecation
-                blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400));
-            } else {
-                //noinspection deprecation
+            SpannableStringBuilder webViewVersionStringBuilder = new SpannableStringBuilder(webViewVersionLabel + webView);
+            SpannableStringBuilder easyListStringBuilder = new SpannableStringBuilder(easyListLabel + blocklistVersions[0]);
+            SpannableStringBuilder easyPrivacyStringBuilder = new SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1]);
+            SpannableStringBuilder fanboyAnnoyanceStringBuilder = new SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2]);
+            SpannableStringBuilder fanboySocialStringBuilder = new SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3]);
+            SpannableStringBuilder ultraListStringBuilder = new SpannableStringBuilder(ultraListLabel + blocklistVersions[4]);
+            SpannableStringBuilder ultraPrivacyStringBuilder = new SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5]);
+
+            // Set the blue color span according to the theme.  The deprecated `getResources()` must be used until the minimum API >= 23.
+            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
                 blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
+            } else {
+                blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.violet_500));
             }
 
             // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
@@ -204,28 +282,40 @@ public class AboutTabFragment extends Fragment {
             bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length(), bootloaderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             androidStringBuilder.setSpan(blueColorSpan, androidLabel.length(), androidStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             buildStringBuilder.setSpan(blueColorSpan, buildLabel.length(), buildStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-            webKitStringBuilder.setSpan(blueColorSpan, webKitLabel.length(), webKitStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-            chromeStringBuilder.setSpan(blueColorSpan, chromeLabel.length(), chromeStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length(), webViewVersionStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length(), easyListStringBuilder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
             easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length(), easyPrivacyStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length(), fanboyAnnoyanceStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length(), fanboySocialStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length(), ultraListStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length(), ultraPrivacyStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
 
             // Display the strings in the text boxes.
-            versionNumberTextView.setText(version);
-            versionBrandTextView.setText(brandStringBuilder);
-            versionManufacturerTextView.setText(manufacturerStringBuilder);
-            versionModelTextView.setText(modelStringBuilder);
-            versionDeviceTextView.setText(deviceStringBuilder);
-            versionBootloaderTextView.setText(bootloaderStringBuilder);
-            versionAndroidTextView.setText(androidStringBuilder);
-            versionBuildTextView.setText(buildStringBuilder);
-            versionWebKitTextView.setText(webKitStringBuilder);
-            versionChromeTextView.setText(chromeStringBuilder);
-            versionEasyListTextView.setText(easyListStringBuilder);
-            versionEasyPrivacyTextView.setText(easyPrivacyStringBuilder);
-            versionFanboyAnnoyanceTextView.setText(fanboyAnnoyanceStringBuilder);
-            versionFanboySocialTextView.setText(fanboySocialStringBuilder);
+            versionTextView.setText(version);
+            brandTextView.setText(brandStringBuilder);
+            manufacturerTextView.setText(manufacturerStringBuilder);
+            modelTextView.setText(modelStringBuilder);
+            deviceTextView.setText(deviceStringBuilder);
+            bootloaderTextView.setText(bootloaderStringBuilder);
+            androidTextView.setText(androidStringBuilder);
+            buildTextView.setText(buildStringBuilder);
+            webViewVersionTextView.setText(webViewVersionStringBuilder);
+            easyListTextView.setText(easyListStringBuilder);
+            easyPrivacyTextView.setText(easyPrivacyStringBuilder);
+            fanboyAnnoyanceTextView.setText(fanboyAnnoyanceStringBuilder);
+            fanboySocialTextView.setText(fanboySocialStringBuilder);
+            ultraListTextView.setText(ultraListStringBuilder);
+            ultraPrivacyTextView.setText(ultraPrivacyStringBuilder);
+
+            // Only populate the radio text view if there is a radio in the device.
+            if (!radio.isEmpty()) {
+                String radioLabel = getString(R.string.radio) + "  ";
+                SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio);
+                radioStringBuilder.setSpan(blueColorSpan, radioLabel.length(), radioStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                radioTextView.setText(radioStringBuilder);
+            } else {  // This device does not have a radio.
+                radioTextView.setVisibility(View.GONE);
+            }
 
             // Build.VERSION.SECURITY_PATCH is only available for SDK_INT >= 23.
             if (Build.VERSION.SDK_INT >= 23) {
@@ -233,37 +323,76 @@ public class AboutTabFragment extends Fragment {
                 String securityPatch = Build.VERSION.SECURITY_PATCH;
                 SpannableStringBuilder securityPatchStringBuilder = new SpannableStringBuilder(securityPatchLabel + securityPatch);
                 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length(), securityPatchStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                versionSecurityPatchTextView.setText(securityPatchStringBuilder);
-            } else {  // SDK_INT < 23, so `versionSecurityPatchTextView` should be hidden.
-                versionSecurityPatchTextView.setVisibility(View.GONE);
+                securityPatchTextView.setText(securityPatchStringBuilder);
+            } else {  // The API < 23.
+                // Hide the security patch text view.
+                securityPatchTextView.setVisibility(View.GONE);
             }
 
-            // Only populate `versionRadioTextView` if there is a radio in the device.
-            if (!radio.equals("")) {
-                String radioLabel = getString(R.string.radio) + "  ";
-                SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio);
-                radioStringBuilder.setSpan(blueColorSpan, radioLabel.length(), radioStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                versionRadioTextView.setText(radioStringBuilder);
-            } else {  // This device does not have a radio, so `versionRadioTextView` should be hidden.
-                versionRadioTextView.setVisibility(View.GONE);
+            // Only populate the WebView provider if the SDK >= 21.
+            if (Build.VERSION.SDK_INT >= 21) {
+                // Create the WebView provider label.
+                String webViewProviderLabel = getString(R.string.webview_provider) + "  ";
+
+                // Get the current WebView package info.
+                PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(context);
+
+                // Remove the warning below that the package info might be null.
+                assert webViewPackageInfo != null;
+
+                // Get the WebView provider name.
+                String webViewPackageName = webViewPackageInfo.packageName;
+
+                // Create the spannable string builder.
+                SpannableStringBuilder webViewProviderStringBuilder = new SpannableStringBuilder(webViewProviderLabel + webViewPackageName);
+
+                // Apply the coloration.
+                webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length(), webViewProviderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+                // Display the WebView provider.
+                webViewProviderTextView.setText(webViewProviderStringBuilder);
+            } else {  // The API < 21.
+                // Hide the WebView provider text view.
+                webViewProviderTextView.setVisibility(View.GONE);
             }
 
-            // Only populate `versionOrbotTextView` if Orbot is installed.
-            if (!orbot.equals("")) {
+            // Only populate the Orbot text view if it is installed.
+            if (!orbot.isEmpty()) {
                 String orbotLabel = getString(R.string.orbot) + "  ";
                 SpannableStringBuilder orbotStringBuilder = new SpannableStringBuilder(orbotLabel + orbot);
                 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length(), orbotStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                versionOrbotTextView.setText(orbotStringBuilder);
-            } else {  // Orbot is not installed, so the `versionOrbotTextView` should be hidden.
-                versionOrbotTextView.setVisibility(View.GONE);
+                orbotTextView.setText(orbotStringBuilder);
+            } else {  // Orbot is not installed.
+                orbotTextView.setVisibility(View.GONE);
+            }
+
+            // Only populate the I2P text view if it is installed.
+            if (!i2p.isEmpty()) {
+                String i2pLabel = getString(R.string.i2p)  + "  ";
+                SpannableStringBuilder i2pStringBuilder = new SpannableStringBuilder(i2pLabel + i2p);
+                i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length(), i2pStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                i2pTextView.setText(i2pStringBuilder);
+            } else {  // I2P is not installed.
+                i2pTextView.setVisibility(View.GONE);
+            }
+
+            // Only populate the OpenKeychain text view if it is installed.
+            if (!openKeychain.isEmpty()) {
+                String openKeychainLabel = getString(R.string.openkeychain) + "  ";
+                SpannableStringBuilder openKeychainStringBuilder = new SpannableStringBuilder(openKeychainLabel + openKeychain);
+                openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length(), openKeychainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                openKeychainTextView.setText(openKeychainStringBuilder);
+            } else {  //OpenKeychain is not installed.
+                openKeychainTextView.setVisibility(View.GONE);
             }
 
             // Display the package signature.
             try {
                 // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
-                @SuppressLint("PackageManagerGetSignatures") Signature packageSignature = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
+                @SuppressLint("PackageManagerGetSignatures") Signature packageSignature = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(),
+                        PackageManager.GET_SIGNATURES).signatures[0];
 
-                // Convert the signature to a `byte[]` `InputStream`.
+                // Convert the signature to a byte array input stream.
                 InputStream certificateByteArrayInputStream = new ByteArrayInputStream(packageSignature.toByteArray());
 
                 // Display the certificate information on the screen.
@@ -312,20 +441,47 @@ public class AboutTabFragment extends Fragment {
                 } catch (CertificateException e) {
                     // Do nothing if there is a certificate error.
                 }
+
+                // Get a handle for the runtime.
+                runtime = Runtime.getRuntime();
+
+                // Get a handle for the activity.
+                Activity activity = getActivity();
+
+                // Remove the incorrect lint warning below that the activity might be null.
+                assert activity != null;
+
+                // Get a handle for the activity manager.
+                activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
+
+                // Remove the incorrect lint warning below that the activity manager might be null.
+                assert activityManager != null;
+
+                // Instantiate a memory info variable.
+                memoryInfo = new ActivityManager.MemoryInfo();
+
+                // Define a number format.
+                numberFormat = NumberFormat.getInstance();
+
+                // Set the minimum and maximum number of fraction digits.
+                numberFormat.setMinimumFractionDigits(2);
+                numberFormat.setMaximumFractionDigits(2);
+
+                // Update the memory usage.
+                updateMemoryUsage(getActivity());
             } catch (PackageManager.NameNotFoundException e) {
                 // Do nothing if `PackageManager` says Privacy Browser isn't installed.
             }
-        } else { // load a `WebView` for all the other tabs.  Tab numbers start at 0.
+        } else { // load a WebView for all the other tabs.  Tab numbers start at 0.
             // 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.
-            tabLayout = inflater.inflate(R.layout.bare_webview, container, false);
+            tabLayout = layoutInflater.inflate(R.layout.bare_webview, container, false);
 
             // Get a handle for `tabWebView`.
             WebView tabWebView = (WebView) tabLayout;
 
             // Load the tabs according to the theme.
-            if (MainWebViewActivity.darkTheme) {  // The dark theme is applied.
-                // Set the background color.  We have to use the deprecated `.getColor()` until API >= 23.
-                //noinspection deprecation
+            if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {  // The dark theme is applied.
+                // Set the background color.  The deprecated `.getColor()` must be used until the minimum API >= 23.
                 tabWebView.setBackgroundColor(getResources().getColor(R.color.gray_850));
 
                 switch (tabNumber) {
@@ -382,7 +538,126 @@ 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 onPause() {
+        // Run the default commands.
+        super.onPause();
+
+        // Pause the updating of the memory usage.
+        updateMemoryUsageBoolean = false;
+    }
+
+    @Override
+    public void onResume() {
+        // Run the default commands.
+        super.onResume();
+
+        // Resume the updating of the memory usage.
+        updateMemoryUsageBoolean = true;
+    }
+
+    @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());
+        }
+    }
+
+    public void updateMemoryUsage(Activity activity) {
+        try {
+            // Update the memory usage if enabled.
+            if (updateMemoryUsageBoolean) {
+                // Populate the memory info variable.
+                activityManager.getMemoryInfo(memoryInfo);
+
+                // Get the app memory information.
+                long appAvailableMemoryLong = runtime.freeMemory();
+                long appTotalMemoryLong = runtime.totalMemory();
+                long appMaximumMemoryLong = runtime.maxMemory();
+
+                // Calculate the app consumed memory.
+                long appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong;
+
+                // Get the system memory information.
+                long systemTotalMemoryLong = memoryInfo.totalMem;
+                long systemAvailableMemoryLong = memoryInfo.availMem;
+
+                // Calculate the system consumed memory.
+                long systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong;
+
+                // Convert the memory information into mebibytes.
+                float appConsumedMemoryFloat = (float) appConsumedMemoryLong / MEBIBYTE;
+                float appAvailableMemoryFloat = (float) appAvailableMemoryLong / MEBIBYTE;
+                float appTotalMemoryFloat = (float) appTotalMemoryLong / MEBIBYTE;
+                float appMaximumMemoryFloat = (float) appMaximumMemoryLong / MEBIBYTE;
+                float systemConsumedMemoryFloat = (float) systemConsumedMemoryLong / MEBIBYTE;
+                float systemAvailableMemoryFloat = (float) systemAvailableMemoryLong / MEBIBYTE;
+                float systemTotalMemoryFloat = (float) systemTotalMemoryLong / MEBIBYTE;
+
+                // Get the mebibyte string.
+                String mebibyte = getString(R.string.mebibyte);
+
+                // Calculate the mebibyte length.
+                int mebibyteLength = mebibyte.length();
+
+                // Create spannable string builders.
+                SpannableStringBuilder appConsumedMemoryStringBuilder = new SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat) + " " + mebibyte);
+                SpannableStringBuilder appAvailableMemoryStringBuilder = new SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat) + " " + mebibyte);
+                SpannableStringBuilder appTotalMemoryStringBuilder = new SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat) + " " + mebibyte);
+                SpannableStringBuilder appMaximumMemoryStringBuilder = new SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat) + " " + mebibyte);
+                SpannableStringBuilder systemConsumedMemoryStringBuilder = new SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat) + " " + mebibyte);
+                SpannableStringBuilder systemAvailableMemoryStringBuilder = new SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat) + " " + mebibyte);
+                SpannableStringBuilder systemTotalMemoryStringBuilder = new SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat) + " " + mebibyte);
+
+                // Setup the spans to display the memory information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+                appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length(), appConsumedMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length(), appAvailableMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length(), appTotalMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length(), appMaximumMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length(), systemConsumedMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length(), systemAvailableMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length(), systemTotalMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+                // Display the string in the text boxes.
+                appConsumedMemoryTextView.setText(appConsumedMemoryStringBuilder);
+                appAvailableMemoryTextView.setText(appAvailableMemoryStringBuilder);
+                appTotalMemoryTextView.setText(appTotalMemoryStringBuilder);
+                appMaximumMemoryTextView.setText(appMaximumMemoryStringBuilder);
+                systemConsumedMemoryTextView.setText(systemConsumedMemoryStringBuilder);
+                systemAvailableMemoryTextView.setText(systemAvailableMemoryStringBuilder);
+                systemTotalMemoryTextView.setText(systemTotalMemoryStringBuilder);
+            }
+
+            // Schedule another memory update if the activity has not been destroyed.
+            if (!activity.isDestroyed()) {
+                // Create a handler to update the memory usage.
+                Handler updateMemoryUsageHandler = new Handler();
+
+                // Create a runnable to update the memory usage.
+                Runnable updateMemoryUsageRunnable = () -> updateMemoryUsage(activity);
+
+                // Update the memory usage after 1000 milliseconds
+                updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000);
+            }
+        } catch (Exception exception) {
+            // Do nothing.
+        }
+    }
 }
\ No newline at end of file