Display memory usage information in About > Version. https://redmine.stoutner.com...
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / fragments / AboutTabFragment.java
1 /*
2  * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.fragments;
21
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.app.ActivityManager;
25 import android.content.Context;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.Signature;
29 import android.content.res.Configuration;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.text.SpannableStringBuilder;
34 import android.text.Spanned;
35 import android.text.style.ForegroundColorSpan;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.webkit.WebView;
40 import android.widget.TextView;
41
42 import androidx.annotation.NonNull;
43 import androidx.fragment.app.Fragment;
44 import androidx.webkit.WebViewCompat;
45
46 import com.stoutner.privacybrowser.BuildConfig;
47 import com.stoutner.privacybrowser.R;
48
49 import java.io.ByteArrayInputStream;
50 import java.io.InputStream;
51 import java.math.BigInteger;
52 import java.security.Principal;
53 import java.security.cert.CertificateException;
54 import java.security.cert.CertificateFactory;
55 import java.security.cert.X509Certificate;
56 import java.text.DateFormat;
57 import java.text.NumberFormat;
58 import java.util.Date;
59
60 public class AboutTabFragment extends Fragment {
61     // Declare the class constants.
62     final static String TAB_NUMBER = "tab_number";
63     final static String BLOCKLIST_VERSIONS = "blocklist_versions";
64     final long MEBIBYTE = 1048576;
65
66     // Declare the class variables.
67     private boolean updateMemoryUsageBoolean = true;
68     private int tabNumber;
69     private String[] blocklistVersions;
70     private View tabLayout;
71     private String appConsumedMemoryLabel;
72     private String appAvailableMemoryLabel;
73     private String appTotalMemoryLabel;
74     private String appMaximumMemoryLabel;
75     private String systemConsumedMemoryLabel;
76     private String systemAvailableMemoryLabel;
77     private String systemTotalMemoryLabel;
78     private Runtime runtime;
79     private ActivityManager activityManager;
80     private ActivityManager.MemoryInfo memoryInfo;
81     private NumberFormat numberFormat;
82     private ForegroundColorSpan blueColorSpan;
83
84     // Declare the class views.
85     private TextView appConsumedMemoryTextView;
86     private TextView appAvailableMemoryTextView;
87     private TextView appTotalMemoryTextView;
88     private TextView appMaximumMemoryTextView;
89     private TextView systemConsumedMemoryTextView;
90     private TextView systemAvailableMemoryTextView;
91     private TextView systemTotalMemoryTextView;
92
93     public static AboutTabFragment createTab(int tabNumber, String[] blocklistVersions) {
94         // Create a bundle.
95         Bundle argumentsBundle = new Bundle();
96
97         // Store the tab number in the bundle.
98         argumentsBundle.putInt(TAB_NUMBER, tabNumber);
99         argumentsBundle.putStringArray(BLOCKLIST_VERSIONS, blocklistVersions);
100
101         // Create a new instance of the tab fragment.
102         AboutTabFragment aboutTabFragment = new AboutTabFragment();
103
104         // Add the arguments bundle to the fragment.
105         aboutTabFragment.setArguments(argumentsBundle);
106
107         // Return the new fragment.
108         return aboutTabFragment;
109     }
110
111     @Override
112     public void onCreate(Bundle savedInstanceState) {
113         // Run the default commands.
114         super.onCreate(savedInstanceState);
115
116         // Get a handle for the arguments.
117         Bundle arguments = getArguments();
118
119         // Remove the incorrect lint warning below that arguments might be null.
120         assert arguments != null;
121
122         // Store the arguments in class variables.
123         tabNumber = getArguments().getInt(TAB_NUMBER);
124         blocklistVersions = getArguments().getStringArray(BLOCKLIST_VERSIONS);
125     }
126
127     @Override
128     public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
129         // Get a handle for the context and assert that it isn't null.
130         Context context = getContext();
131         assert context != null;
132
133         // Get the current theme status.
134         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
135
136         // Load the tabs.  Tab numbers start at 0.
137         if (tabNumber == 0) {  // Load the about tab.
138             // 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.
139             tabLayout = layoutInflater.inflate(R.layout.about_tab_version, container, false);
140
141             // Get handles for the text views.
142             TextView versionTextView = tabLayout.findViewById(R.id.version);
143             TextView brandTextView = tabLayout.findViewById(R.id.brand);
144             TextView manufacturerTextView = tabLayout.findViewById(R.id.manufacturer);
145             TextView modelTextView = tabLayout.findViewById(R.id.model);
146             TextView deviceTextView = tabLayout.findViewById(R.id.device);
147             TextView bootloaderTextView = tabLayout.findViewById(R.id.bootloader);
148             TextView radioTextView = tabLayout.findViewById(R.id.radio);
149             TextView androidTextView = tabLayout.findViewById(R.id.android);
150             TextView securityPatchTextView = tabLayout.findViewById(R.id.security_patch);
151             TextView buildTextView = tabLayout.findViewById(R.id.build);
152             TextView webViewProviderTextView = tabLayout.findViewById(R.id.webview_provider);
153             TextView webViewVersionTextView = tabLayout.findViewById(R.id.webview_version);
154             TextView orbotTextView = tabLayout.findViewById(R.id.orbot);
155             TextView i2pTextView = tabLayout.findViewById(R.id.i2p);
156             TextView openKeychainTextView = tabLayout.findViewById(R.id.open_keychain);
157             appConsumedMemoryTextView = tabLayout.findViewById(R.id.app_consumed_memory);
158             appAvailableMemoryTextView = tabLayout.findViewById(R.id.app_available_memory);
159             appTotalMemoryTextView = tabLayout.findViewById(R.id.app_total_memory);
160             appMaximumMemoryTextView = tabLayout.findViewById(R.id.app_maximum_memory);
161             systemConsumedMemoryTextView = tabLayout.findViewById(R.id.system_consumed_memory);
162             systemAvailableMemoryTextView = tabLayout.findViewById(R.id.system_available_memory);
163             systemTotalMemoryTextView = tabLayout.findViewById(R.id.system_total_memory);
164             TextView easyListTextView = tabLayout.findViewById(R.id.easylist);
165             TextView easyPrivacyTextView = tabLayout.findViewById(R.id.easyprivacy);
166             TextView fanboyAnnoyanceTextView = tabLayout.findViewById(R.id.fanboy_annoyance);
167             TextView fanboySocialTextView = tabLayout.findViewById(R.id.fanboy_social);
168             TextView ultraListTextView = tabLayout.findViewById(R.id.ultralist);
169             TextView ultraPrivacyTextView = tabLayout.findViewById(R.id.ultraprivacy);
170             TextView certificateIssuerDNTextView = tabLayout.findViewById(R.id.certificate_issuer_dn);
171             TextView certificateSubjectDNTextView = tabLayout.findViewById(R.id.certificate_subject_dn);
172             TextView certificateStartDateTextView = tabLayout.findViewById(R.id.certificate_start_date);
173             TextView certificateEndDateTextView = tabLayout.findViewById(R.id.certificate_end_date);
174             TextView certificateVersionTextView = tabLayout.findViewById(R.id.certificate_version);
175             TextView certificateSerialNumberTextView = tabLayout.findViewById(R.id.certificate_serial_number);
176             TextView certificateSignatureAlgorithmTextView = tabLayout.findViewById(R.id.certificate_signature_algorithm);
177
178             // Setup the labels.
179             String version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + BuildConfig.VERSION_CODE + ")";
180             String brandLabel = getString(R.string.brand) + "  ";
181             String manufacturerLabel = getString(R.string.manufacturer) + "  ";
182             String modelLabel = getString(R.string.model) + "  ";
183             String deviceLabel = getString(R.string.device) + "  ";
184             String bootloaderLabel = getString(R.string.bootloader) + "  ";
185             String androidLabel = getString(R.string.android) + "  ";
186             String buildLabel = getString(R.string.build) + "  ";
187             String webViewVersionLabel = getString(R.string.webview_version) + "  ";
188             appConsumedMemoryLabel = getString(R.string.app_consumed_memory) + "  ";
189             appAvailableMemoryLabel = getString(R.string.app_available_memory) + "  ";
190             appTotalMemoryLabel = getString(R.string.app_total_memory) + "  ";
191             appMaximumMemoryLabel = getString(R.string.app_maximum_memory) + "  ";
192             systemConsumedMemoryLabel = getString(R.string.system_consumed_memory) + "  ";
193             systemAvailableMemoryLabel = getString(R.string.system_available_memory) + "  ";
194             systemTotalMemoryLabel = getString(R.string.system_total_memory) + "  ";
195             String easyListLabel = getString(R.string.easylist_label) + "  ";
196             String easyPrivacyLabel = getString(R.string.easyprivacy_label) + "  ";
197             String fanboyAnnoyanceLabel = getString(R.string.fanboy_annoyance_label) + "  ";
198             String fanboySocialLabel = getString(R.string.fanboy_social_label) + "  ";
199             String ultraListLabel = getString(R.string.ultralist_label) + "  ";
200             String ultraPrivacyLabel = getString(R.string.ultraprivacy_label) + "  ";
201             String issuerDNLabel = getString(R.string.issuer_dn) + "  ";
202             String subjectDNLabel = getString(R.string.subject_dn) + "  ";
203             String startDateLabel = getString(R.string.start_date) + "  ";
204             String endDateLabel = getString(R.string.end_date) + "  ";
205             String certificateVersionLabel = getString(R.string.certificate_version) + "  ";
206             String serialNumberLabel = getString(R.string.serial_number) + "  ";
207             String signatureAlgorithmLabel = getString(R.string.signature_algorithm) + "  ";
208
209             // 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.
210             // Once the minimum API >= 26 this can be accomplished with the WebView package info.
211             View webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false);
212             WebView tabLayoutWebView = webViewLayout.findViewById(R.id.bare_webview);
213             String userAgentString =  tabLayoutWebView.getSettings().getUserAgentString();
214
215             // Get the device's information and store it in strings.
216             String brand = Build.BRAND;
217             String manufacturer = Build.MANUFACTURER;
218             String model = Build.MODEL;
219             String device = Build.DEVICE;
220             String bootloader = Build.BOOTLOADER;
221             String radio = Build.getRadioVersion();
222             String android = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Build.VERSION.SDK_INT + ")";
223             String build = Build.DISPLAY;
224             // Select the substring that begins after `Chrome/` and goes until the next ` `.
225             String webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")));
226
227             // Get the Orbot version name if Orbot is installed.
228             String orbot;
229             try {
230                 // Store the version name.
231                 orbot = context.getPackageManager().getPackageInfo("org.torproject.android", 0).versionName;
232             } catch (PackageManager.NameNotFoundException exception) {  // Orbot is not installed.
233                 orbot = "";
234             }
235
236             // Get the I2P version name if I2P is installed.
237             String i2p;
238             try {
239                 // Store the version name.
240                 i2p = context.getPackageManager().getPackageInfo("net.i2p.android.router", 0).versionName;
241             } catch (PackageManager.NameNotFoundException exception) {  // I2P is not installed.
242                 i2p = "";
243             }
244
245             // Get the OpenKeychain version name if it is installed.
246             String openKeychain;
247             try {
248                 // Store the version name.
249                 openKeychain = context.getPackageManager().getPackageInfo("org.sufficientlysecure.keychain", 0).versionName;
250             } catch (PackageManager.NameNotFoundException exception) {  // OpenKeychain is not installed.
251                 openKeychain = "";
252             }
253
254             // Create a spannable string builder for the hardware and software text views that needs multiple colors of text.
255             SpannableStringBuilder brandStringBuilder = new SpannableStringBuilder(brandLabel + brand);
256             SpannableStringBuilder manufacturerStringBuilder = new SpannableStringBuilder(manufacturerLabel + manufacturer);
257             SpannableStringBuilder modelStringBuilder = new SpannableStringBuilder(modelLabel + model);
258             SpannableStringBuilder deviceStringBuilder = new SpannableStringBuilder(deviceLabel + device);
259             SpannableStringBuilder bootloaderStringBuilder = new SpannableStringBuilder(bootloaderLabel + bootloader);
260             SpannableStringBuilder androidStringBuilder = new SpannableStringBuilder(androidLabel + android);
261             SpannableStringBuilder buildStringBuilder = new SpannableStringBuilder(buildLabel + build);
262             SpannableStringBuilder webViewVersionStringBuilder = new SpannableStringBuilder(webViewVersionLabel + webView);
263             SpannableStringBuilder easyListStringBuilder = new SpannableStringBuilder(easyListLabel + blocklistVersions[0]);
264             SpannableStringBuilder easyPrivacyStringBuilder = new SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1]);
265             SpannableStringBuilder fanboyAnnoyanceStringBuilder = new SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2]);
266             SpannableStringBuilder fanboySocialStringBuilder = new SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3]);
267             SpannableStringBuilder ultraListStringBuilder = new SpannableStringBuilder(ultraListLabel + blocklistVersions[4]);
268             SpannableStringBuilder ultraPrivacyStringBuilder = new SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5]);
269
270             // Set the blue color span according to the theme.  The deprecated `getResources()` must be used until the minimum API >= 23.
271             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
272                 blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
273             } else {
274                 blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.violet_500));
275             }
276
277             // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
278             brandStringBuilder.setSpan(blueColorSpan, brandLabel.length(), brandStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
279             manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length(), manufacturerStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
280             modelStringBuilder.setSpan(blueColorSpan, modelLabel.length(), modelStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
281             deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length(), deviceStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
282             bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length(), bootloaderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
283             androidStringBuilder.setSpan(blueColorSpan, androidLabel.length(), androidStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
284             buildStringBuilder.setSpan(blueColorSpan, buildLabel.length(), buildStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
285             webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length(), webViewVersionStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
286             easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length(), easyListStringBuilder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
287             easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length(), easyPrivacyStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
288             fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length(), fanboyAnnoyanceStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
289             fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length(), fanboySocialStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
290             ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length(), ultraListStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
291             ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length(), ultraPrivacyStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
292
293             // Display the strings in the text boxes.
294             versionTextView.setText(version);
295             brandTextView.setText(brandStringBuilder);
296             manufacturerTextView.setText(manufacturerStringBuilder);
297             modelTextView.setText(modelStringBuilder);
298             deviceTextView.setText(deviceStringBuilder);
299             bootloaderTextView.setText(bootloaderStringBuilder);
300             androidTextView.setText(androidStringBuilder);
301             buildTextView.setText(buildStringBuilder);
302             webViewVersionTextView.setText(webViewVersionStringBuilder);
303             easyListTextView.setText(easyListStringBuilder);
304             easyPrivacyTextView.setText(easyPrivacyStringBuilder);
305             fanboyAnnoyanceTextView.setText(fanboyAnnoyanceStringBuilder);
306             fanboySocialTextView.setText(fanboySocialStringBuilder);
307             ultraListTextView.setText(ultraListStringBuilder);
308             ultraPrivacyTextView.setText(ultraPrivacyStringBuilder);
309
310             // Only populate the radio text view if there is a radio in the device.
311             if (!radio.isEmpty()) {
312                 String radioLabel = getString(R.string.radio) + "  ";
313                 SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio);
314                 radioStringBuilder.setSpan(blueColorSpan, radioLabel.length(), radioStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
315                 radioTextView.setText(radioStringBuilder);
316             } else {  // This device does not have a radio.
317                 radioTextView.setVisibility(View.GONE);
318             }
319
320             // Build.VERSION.SECURITY_PATCH is only available for SDK_INT >= 23.
321             if (Build.VERSION.SDK_INT >= 23) {
322                 String securityPatchLabel = getString(R.string.security_patch) + "  ";
323                 String securityPatch = Build.VERSION.SECURITY_PATCH;
324                 SpannableStringBuilder securityPatchStringBuilder = new SpannableStringBuilder(securityPatchLabel + securityPatch);
325                 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length(), securityPatchStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
326                 securityPatchTextView.setText(securityPatchStringBuilder);
327             } else {  // The API < 23.
328                 // Hide the security patch text view.
329                 securityPatchTextView.setVisibility(View.GONE);
330             }
331
332             // Only populate the WebView provider if the SDK >= 21.
333             if (Build.VERSION.SDK_INT >= 21) {
334                 // Create the WebView provider label.
335                 String webViewProviderLabel = getString(R.string.webview_provider) + "  ";
336
337                 // Get the current WebView package info.
338                 PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(context);
339
340                 // Remove the warning below that the package info might be null.
341                 assert webViewPackageInfo != null;
342
343                 // Get the WebView provider name.
344                 String webViewPackageName = webViewPackageInfo.packageName;
345
346                 // Create the spannable string builder.
347                 SpannableStringBuilder webViewProviderStringBuilder = new SpannableStringBuilder(webViewProviderLabel + webViewPackageName);
348
349                 // Apply the coloration.
350                 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length(), webViewProviderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
351
352                 // Display the WebView provider.
353                 webViewProviderTextView.setText(webViewProviderStringBuilder);
354             } else {  // The API < 21.
355                 // Hide the WebView provider text view.
356                 webViewProviderTextView.setVisibility(View.GONE);
357             }
358
359             // Only populate the Orbot text view if it is installed.
360             if (!orbot.isEmpty()) {
361                 String orbotLabel = getString(R.string.orbot) + "  ";
362                 SpannableStringBuilder orbotStringBuilder = new SpannableStringBuilder(orbotLabel + orbot);
363                 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length(), orbotStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
364                 orbotTextView.setText(orbotStringBuilder);
365             } else {  // Orbot is not installed.
366                 orbotTextView.setVisibility(View.GONE);
367             }
368
369             // Only populate the I2P text view if it is installed.
370             if (!i2p.isEmpty()) {
371                 String i2pLabel = getString(R.string.i2p)  + "  ";
372                 SpannableStringBuilder i2pStringBuilder = new SpannableStringBuilder(i2pLabel + i2p);
373                 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length(), i2pStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
374                 i2pTextView.setText(i2pStringBuilder);
375             } else {  // I2P is not installed.
376                 i2pTextView.setVisibility(View.GONE);
377             }
378
379             // Only populate the OpenKeychain text view if it is installed.
380             if (!openKeychain.isEmpty()) {
381                 String openKeychainLabel = getString(R.string.openkeychain) + "  ";
382                 SpannableStringBuilder openKeychainStringBuilder = new SpannableStringBuilder(openKeychainLabel + openKeychain);
383                 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length(), openKeychainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
384                 openKeychainTextView.setText(openKeychainStringBuilder);
385             } else {  //OpenKeychain is not installed.
386                 openKeychainTextView.setVisibility(View.GONE);
387             }
388
389             // Display the package signature.
390             try {
391                 // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
392                 @SuppressLint("PackageManagerGetSignatures") Signature packageSignature = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(),
393                         PackageManager.GET_SIGNATURES).signatures[0];
394
395                 // Convert the signature to a byte array input stream.
396                 InputStream certificateByteArrayInputStream = new ByteArrayInputStream(packageSignature.toByteArray());
397
398                 // Display the certificate information on the screen.
399                 try {
400                     // Instantiate a `CertificateFactory`.
401                     CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
402
403                     // Generate an `X509Certificate`.
404                     X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(certificateByteArrayInputStream);
405
406                     // Store the individual sections of the certificate that we are interested in.
407                     Principal issuerDNPrincipal = x509Certificate.getIssuerDN();
408                     Principal subjectDNPrincipal = x509Certificate.getSubjectDN();
409                     Date startDate = x509Certificate.getNotBefore();
410                     Date endDate = x509Certificate.getNotAfter();
411                     int certificateVersion = x509Certificate.getVersion();
412                     BigInteger serialNumberBigInteger = x509Certificate.getSerialNumber();
413                     String signatureAlgorithmNameString = x509Certificate.getSigAlgName();
414
415                     // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text.
416                     SpannableStringBuilder issuerDNStringBuilder = new SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString());
417                     SpannableStringBuilder subjectDNStringBuilder = new SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString());
418                     SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate));
419                     SpannableStringBuilder endDataStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate));
420                     SpannableStringBuilder certificateVersionStringBuilder = new SpannableStringBuilder(certificateVersionLabel + certificateVersion);
421                     SpannableStringBuilder serialNumberStringBuilder = new SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger);
422                     SpannableStringBuilder signatureAlgorithmStringBuilder = new SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString);
423
424                     // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
425                     issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length(), issuerDNStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
426                     subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length(), subjectDNStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
427                     startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
428                     endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDataStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
429                     certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length(), certificateVersionStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
430                     serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length(), serialNumberStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
431                     signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length(), signatureAlgorithmStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
432
433                     // Display the strings in the text boxes.
434                     certificateIssuerDNTextView.setText(issuerDNStringBuilder);
435                     certificateSubjectDNTextView.setText(subjectDNStringBuilder);
436                     certificateStartDateTextView.setText(startDateStringBuilder);
437                     certificateEndDateTextView.setText(endDataStringBuilder);
438                     certificateVersionTextView.setText(certificateVersionStringBuilder);
439                     certificateSerialNumberTextView.setText(serialNumberStringBuilder);
440                     certificateSignatureAlgorithmTextView.setText(signatureAlgorithmStringBuilder);
441                 } catch (CertificateException e) {
442                     // Do nothing if there is a certificate error.
443                 }
444
445                 // Get a handle for the runtime.
446                 runtime = Runtime.getRuntime();
447
448                 // Get a handle for the activity.
449                 Activity activity = getActivity();
450
451                 // Remove the incorrect lint warning below that the activity might be null.
452                 assert activity != null;
453
454                 // Get a handle for the activity manager.
455                 activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
456
457                 // Remove the incorrect lint warning below that the activity manager might be null.
458                 assert activityManager != null;
459
460                 // Instantiate a memory info variable.
461                 memoryInfo = new ActivityManager.MemoryInfo();
462
463                 // Define a number format.
464                 numberFormat = NumberFormat.getInstance();
465
466                 // Set the minimum and maximum number of fraction digits.
467                 numberFormat.setMinimumFractionDigits(2);
468                 numberFormat.setMaximumFractionDigits(2);
469
470                 // Update the memory usage.
471                 updateMemoryUsage(getActivity());
472             } catch (PackageManager.NameNotFoundException e) {
473                 // Do nothing if `PackageManager` says Privacy Browser isn't installed.
474             }
475         } else { // load a WebView for all the other tabs.  Tab numbers start at 0.
476             // 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.
477             tabLayout = layoutInflater.inflate(R.layout.bare_webview, container, false);
478
479             // Get a handle for `tabWebView`.
480             WebView tabWebView = (WebView) tabLayout;
481
482             // Load the tabs according to the theme.
483             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {  // The dark theme is applied.
484                 // Set the background color.  The deprecated `.getColor()` must be used until the minimum API >= 23.
485                 tabWebView.setBackgroundColor(getResources().getColor(R.color.gray_850));
486
487                 switch (tabNumber) {
488                     case 1:
489                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_permissions_dark.html");
490                         break;
491
492                     case 2:
493                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_privacy_policy_dark.html");
494                         break;
495
496                     case 3:
497                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_changelog_dark.html");
498                         break;
499
500                     case 4:
501                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_licenses_dark.html");
502                         break;
503
504                     case 5:
505                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_contributors_dark.html");
506                         break;
507
508                     case 6:
509                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_links_dark.html");
510                         break;
511                 }
512             } else {  // The light theme is applied.
513                 switch (tabNumber) {
514                     case 1:
515                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_permissions_light.html");
516                         break;
517
518                     case 2:
519                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_privacy_policy_light.html");
520                         break;
521
522                     case 3:
523                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_changelog_light.html");
524                         break;
525
526                     case 4:
527                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_licenses_light.html");
528                         break;
529
530                     case 5:
531                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_contributors_light.html");
532                         break;
533
534                     case 6:
535                         tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_links_light.html");
536                         break;
537                 }
538             }
539         }
540
541         // Scroll the tab if the saved instance state is not null.
542         if (savedInstanceState != null) {
543             tabLayout.post(() -> {
544                 tabLayout.setScrollX(savedInstanceState.getInt("scroll_x"));
545                 tabLayout.setScrollY(savedInstanceState.getInt("scroll_y"));
546             });
547         }
548
549         // Return the formatted `tabLayout`.
550         return tabLayout;
551     }
552
553     @Override
554     public void onPause() {
555         // Run the default commands.
556         super.onPause();
557
558         // Pause the updating of the memory usage.
559         updateMemoryUsageBoolean = false;
560     }
561
562     @Override
563     public void onResume() {
564         // Run the default commands.
565         super.onResume();
566
567         // Resume the updating of the memory usage.
568         updateMemoryUsageBoolean = true;
569     }
570
571     @Override
572     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
573         // Run the default commands.
574         super.onSaveInstanceState(savedInstanceState);
575
576         // Save the scroll positions if the tab layout is not null, which can happen if a tab is not currently selected.
577         if (tabLayout != null) {
578             savedInstanceState.putInt("scroll_x", tabLayout.getScrollX());
579             savedInstanceState.putInt("scroll_y", tabLayout.getScrollY());
580         }
581     }
582
583     public void updateMemoryUsage(Activity activity) {
584         try {
585             // Update the memory usage if enabled.
586             if (updateMemoryUsageBoolean) {
587                 // Populate the memory info variable.
588                 activityManager.getMemoryInfo(memoryInfo);
589
590                 // Get the app memory information.
591                 long appAvailableMemoryLong = runtime.freeMemory();
592                 long appTotalMemoryLong = runtime.totalMemory();
593                 long appMaximumMemoryLong = runtime.maxMemory();
594
595                 // Calculate the app consumed memory.
596                 long appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong;
597
598                 // Get the system memory information.
599                 long systemTotalMemoryLong = memoryInfo.totalMem;
600                 long systemAvailableMemoryLong = memoryInfo.availMem;
601
602                 // Calculate the system consumed memory.
603                 long systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong;
604
605                 // Convert the memory information into mebibytes.
606                 float appConsumedMemoryFloat = (float) appConsumedMemoryLong / MEBIBYTE;
607                 float appAvailableMemoryFloat = (float) appAvailableMemoryLong / MEBIBYTE;
608                 float appTotalMemoryFloat = (float) appTotalMemoryLong / MEBIBYTE;
609                 float appMaximumMemoryFloat = (float) appMaximumMemoryLong / MEBIBYTE;
610                 float systemConsumedMemoryFloat = (float) systemConsumedMemoryLong / MEBIBYTE;
611                 float systemAvailableMemoryFloat = (float) systemAvailableMemoryLong / MEBIBYTE;
612                 float systemTotalMemoryFloat = (float) systemTotalMemoryLong / MEBIBYTE;
613
614                 // Get the mebibyte string.
615                 String mebibyte = getString(R.string.mebibyte);
616
617                 // Calculate the mebibyte length.
618                 int mebibyteLength = mebibyte.length();
619
620                 // Create spannable string builders.
621                 SpannableStringBuilder appConsumedMemoryStringBuilder = new SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat) + " " + mebibyte);
622                 SpannableStringBuilder appAvailableMemoryStringBuilder = new SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat) + " " + mebibyte);
623                 SpannableStringBuilder appTotalMemoryStringBuilder = new SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat) + " " + mebibyte);
624                 SpannableStringBuilder appMaximumMemoryStringBuilder = new SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat) + " " + mebibyte);
625                 SpannableStringBuilder systemConsumedMemoryStringBuilder = new SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat) + " " + mebibyte);
626                 SpannableStringBuilder systemAvailableMemoryStringBuilder = new SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat) + " " + mebibyte);
627                 SpannableStringBuilder systemTotalMemoryStringBuilder = new SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat) + " " + mebibyte);
628
629                 // Setup the spans to display the memory information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
630                 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length(), appConsumedMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
631                 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length(), appAvailableMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
632                 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length(), appTotalMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
633                 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length(), appMaximumMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
634                 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length(), systemConsumedMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
635                 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length(), systemAvailableMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
636                 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length(), systemTotalMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
637
638                 // Display the string in the text boxes.
639                 appConsumedMemoryTextView.setText(appConsumedMemoryStringBuilder);
640                 appAvailableMemoryTextView.setText(appAvailableMemoryStringBuilder);
641                 appTotalMemoryTextView.setText(appTotalMemoryStringBuilder);
642                 appMaximumMemoryTextView.setText(appMaximumMemoryStringBuilder);
643                 systemConsumedMemoryTextView.setText(systemConsumedMemoryStringBuilder);
644                 systemAvailableMemoryTextView.setText(systemAvailableMemoryStringBuilder);
645                 systemTotalMemoryTextView.setText(systemTotalMemoryStringBuilder);
646             }
647
648             // Schedule another memory update if the activity has not been destroyed.
649             if (!activity.isDestroyed()) {
650                 // Create a handler to update the memory usage.
651                 Handler updateMemoryUsageHandler = new Handler();
652
653                 // Create a runnable to update the memory usage.
654                 Runnable updateMemoryUsageRunnable = () -> updateMemoryUsage(activity);
655
656                 // Update the memory usage after 1000 milliseconds
657                 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000);
658             }
659         } catch (Exception exception) {
660             // Do nothing.
661         }
662     }
663 }