Release 3.9.
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / fragments / AboutVersionFragment.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.ClipData;
26 import android.content.ClipboardManager;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.Signature;
32 import android.content.res.Configuration;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.text.SpannableStringBuilder;
37 import android.text.Spanned;
38 import android.text.style.ForegroundColorSpan;
39 import android.view.LayoutInflater;
40 import android.view.Menu;
41 import android.view.MenuInflater;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.webkit.WebView;
46 import android.widget.TextView;
47
48 import com.google.android.material.snackbar.Snackbar;
49 import com.stoutner.privacybrowser.BuildConfig;
50 import com.stoutner.privacybrowser.R;
51 import com.stoutner.privacybrowser.dialogs.SaveDialog;
52
53 import java.io.ByteArrayInputStream;
54 import java.io.InputStream;
55 import java.math.BigInteger;
56 import java.security.Principal;
57 import java.security.cert.CertificateException;
58 import java.security.cert.CertificateFactory;
59 import java.security.cert.X509Certificate;
60 import java.text.DateFormat;
61 import java.text.NumberFormat;
62 import java.util.Date;
63
64 import androidx.annotation.NonNull;
65 import androidx.fragment.app.DialogFragment;
66 import androidx.fragment.app.Fragment;
67 import androidx.webkit.WebViewCompat;
68
69 public class AboutVersionFragment extends Fragment {
70     // Declare the class constants.
71     final static String BLOCKLIST_VERSIONS = "blocklist_versions";
72     final long MEBIBYTE = 1048576;
73
74     // Declare the class variables.
75     private boolean updateMemoryUsageBoolean = true;
76     private String[] blocklistVersions;
77     private View aboutVersionLayout;
78     private String appConsumedMemoryLabel;
79     private String appAvailableMemoryLabel;
80     private String appTotalMemoryLabel;
81     private String appMaximumMemoryLabel;
82     private String systemConsumedMemoryLabel;
83     private String systemAvailableMemoryLabel;
84     private String systemTotalMemoryLabel;
85     private Runtime runtime;
86     private ActivityManager activityManager;
87     private ActivityManager.MemoryInfo memoryInfo;
88     private NumberFormat numberFormat;
89     private ForegroundColorSpan blueColorSpan;
90
91     // Declare the class views.
92     private TextView privacyBrowserTextView;
93     private TextView versionTextView;
94     private TextView hardwareTextView;
95     private TextView brandTextView;
96     private TextView manufacturerTextView;
97     private TextView modelTextView;
98     private TextView deviceTextView;
99     private TextView bootloaderTextView;
100     private TextView radioTextView;
101     private TextView softwareTextView;
102     private TextView androidTextView;
103     private TextView securityPatchTextView;
104     private TextView buildTextView;
105     private TextView webViewProviderTextView;
106     private TextView webViewVersionTextView;
107     private TextView orbotTextView;
108     private TextView i2pTextView;
109     private TextView openKeychainTextView;
110     private TextView memoryUsageTextView;
111     private TextView appConsumedMemoryTextView;
112     private TextView appAvailableMemoryTextView;
113     private TextView appTotalMemoryTextView;
114     private TextView appMaximumMemoryTextView;
115     private TextView systemConsumedMemoryTextView;
116     private TextView systemAvailableMemoryTextView;
117     private TextView systemTotalMemoryTextView;
118     private TextView blocklistsTextView;
119     private TextView easyListTextView;
120     private TextView easyPrivacyTextView;
121     private TextView fanboyAnnoyanceTextView;
122     private TextView fanboySocialTextView;
123     private TextView ultraListTextView;
124     private TextView ultraPrivacyTextView;
125     private TextView packageSignatureTextView;
126     private TextView certificateIssuerDnTextView;
127     private TextView certificateSubjectDnTextView;
128     private TextView certificateStartDateTextView;
129     private TextView certificateEndDateTextView;
130     private TextView certificateVersionTextView;
131     private TextView certificateSerialNumberTextView;
132     private TextView certificateSignatureAlgorithmTextView;
133
134     public static AboutVersionFragment createTab(String[] blocklistVersions) {
135         // Create an arguments bundle.
136         Bundle argumentsBundle = new Bundle();
137
138         // Store the arguments in the bundle.
139         argumentsBundle.putStringArray(BLOCKLIST_VERSIONS, blocklistVersions);
140
141         // Create a new instance of the tab fragment.
142         AboutVersionFragment aboutVersionFragment = new AboutVersionFragment();
143
144         // Add the arguments bundle to the fragment.
145         aboutVersionFragment.setArguments(argumentsBundle);
146
147         // Return the new fragment.
148         return aboutVersionFragment;
149     }
150
151     @Override
152     public void onCreate(Bundle savedInstanceState) {
153         // Run the default commands.
154         super.onCreate(savedInstanceState);
155
156         // Get a handle for the arguments.
157         Bundle arguments = getArguments();
158
159         // Remove the incorrect lint warning below that the arguments might be null.
160         assert arguments != null;
161
162         // Store the arguments in class variables.
163         blocklistVersions = arguments.getStringArray(BLOCKLIST_VERSIONS);
164
165         // Enable the options menu for this fragment.
166         setHasOptionsMenu(true);
167     }
168
169     @Override
170     public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
171         // Get a handle for the context.
172         Context context = getContext();
173
174         // Remove the incorrect lint warning below that the context might be null.
175         assert context != null;
176
177         // Get the current theme status.
178         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
179
180         // Inflate the layout.  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.
181         aboutVersionLayout = layoutInflater.inflate(R.layout.about_version, container, false);
182
183         // Get handles for the views.
184         privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview);
185         versionTextView = aboutVersionLayout.findViewById(R.id.version);
186         hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware);
187         brandTextView = aboutVersionLayout.findViewById(R.id.brand);
188         manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer);
189         modelTextView = aboutVersionLayout.findViewById(R.id.model);
190         deviceTextView = aboutVersionLayout.findViewById(R.id.device);
191         bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader);
192         radioTextView = aboutVersionLayout.findViewById(R.id.radio);
193         softwareTextView = aboutVersionLayout.findViewById(R.id.software);
194         androidTextView = aboutVersionLayout.findViewById(R.id.android);
195         securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch);
196         buildTextView = aboutVersionLayout.findViewById(R.id.build);
197         webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider);
198         webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version);
199         orbotTextView = aboutVersionLayout.findViewById(R.id.orbot);
200         i2pTextView = aboutVersionLayout.findViewById(R.id.i2p);
201         openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain);
202         memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage);
203         appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory);
204         appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory);
205         appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory);
206         appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory);
207         systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory);
208         systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory);
209         systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory);
210         blocklistsTextView = aboutVersionLayout.findViewById(R.id.blocklists);
211         easyListTextView = aboutVersionLayout.findViewById(R.id.easylist);
212         easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy);
213         fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance);
214         fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social);
215         ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist);
216         ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy);
217         packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature);
218         certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn);
219         certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn);
220         certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date);
221         certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date);
222         certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version);
223         certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number);
224         certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm);
225
226         // Setup the labels.
227         String version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + BuildConfig.VERSION_CODE + ")";
228         String brandLabel = getString(R.string.brand) + "  ";
229         String manufacturerLabel = getString(R.string.manufacturer) + "  ";
230         String modelLabel = getString(R.string.model) + "  ";
231         String deviceLabel = getString(R.string.device) + "  ";
232         String bootloaderLabel = getString(R.string.bootloader) + "  ";
233         String androidLabel = getString(R.string.android) + "  ";
234         String buildLabel = getString(R.string.build) + "  ";
235         String webViewVersionLabel = getString(R.string.webview_version) + "  ";
236         appConsumedMemoryLabel = getString(R.string.app_consumed_memory) + "  ";
237         appAvailableMemoryLabel = getString(R.string.app_available_memory) + "  ";
238         appTotalMemoryLabel = getString(R.string.app_total_memory) + "  ";
239         appMaximumMemoryLabel = getString(R.string.app_maximum_memory) + "  ";
240         systemConsumedMemoryLabel = getString(R.string.system_consumed_memory) + "  ";
241         systemAvailableMemoryLabel = getString(R.string.system_available_memory) + "  ";
242         systemTotalMemoryLabel = getString(R.string.system_total_memory) + "  ";
243         String easyListLabel = getString(R.string.easylist_label) + "  ";
244         String easyPrivacyLabel = getString(R.string.easyprivacy_label) + "  ";
245         String fanboyAnnoyanceLabel = getString(R.string.fanboy_annoyance_label) + "  ";
246         String fanboySocialLabel = getString(R.string.fanboy_social_label) + "  ";
247         String ultraListLabel = getString(R.string.ultralist_label) + "  ";
248         String ultraPrivacyLabel = getString(R.string.ultraprivacy_label) + "  ";
249         String issuerDNLabel = getString(R.string.issuer_dn) + "  ";
250         String subjectDNLabel = getString(R.string.subject_dn) + "  ";
251         String startDateLabel = getString(R.string.start_date) + "  ";
252         String endDateLabel = getString(R.string.end_date) + "  ";
253         String certificateVersionLabel = getString(R.string.certificate_version) + "  ";
254         String serialNumberLabel = getString(R.string.serial_number) + "  ";
255         String signatureAlgorithmLabel = getString(R.string.signature_algorithm) + "  ";
256
257         // 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.
258         // Once the minimum API >= 26 this can be accomplished with the WebView package info.
259         View webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false);
260         WebView tabLayoutWebView = webViewLayout.findViewById(R.id.bare_webview);
261         String userAgentString =  tabLayoutWebView.getSettings().getUserAgentString();
262
263         // Get the device's information and store it in strings.
264         String brand = Build.BRAND;
265         String manufacturer = Build.MANUFACTURER;
266         String model = Build.MODEL;
267         String device = Build.DEVICE;
268         String bootloader = Build.BOOTLOADER;
269         String radio = Build.getRadioVersion();
270         String android = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Build.VERSION.SDK_INT + ")";
271         String build = Build.DISPLAY;
272         // Select the substring that begins after `Chrome/` and goes until the next ` `.
273         String webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")));
274
275         // Get the Orbot version name if Orbot is installed.
276         String orbot;
277         try {
278             // Store the version name.
279             orbot = context.getPackageManager().getPackageInfo("org.torproject.android", 0).versionName;
280         } catch (PackageManager.NameNotFoundException exception) {  // Orbot is not installed.
281             orbot = "";
282         }
283
284         // Get the I2P version name if I2P is installed.
285         String i2p;
286         try {
287             // Store the version name.
288             i2p = context.getPackageManager().getPackageInfo("net.i2p.android.router", 0).versionName;
289         } catch (PackageManager.NameNotFoundException exception) {  // I2P is not installed.
290             i2p = "";
291         }
292
293         // Get the OpenKeychain version name if it is installed.
294         String openKeychain;
295         try {
296             // Store the version name.
297             openKeychain = context.getPackageManager().getPackageInfo("org.sufficientlysecure.keychain", 0).versionName;
298         } catch (PackageManager.NameNotFoundException exception) {  // OpenKeychain is not installed.
299             openKeychain = "";
300         }
301
302         // Create a spannable string builder for the hardware and software text views that needs multiple colors of text.
303         SpannableStringBuilder brandStringBuilder = new SpannableStringBuilder(brandLabel + brand);
304         SpannableStringBuilder manufacturerStringBuilder = new SpannableStringBuilder(manufacturerLabel + manufacturer);
305         SpannableStringBuilder modelStringBuilder = new SpannableStringBuilder(modelLabel + model);
306         SpannableStringBuilder deviceStringBuilder = new SpannableStringBuilder(deviceLabel + device);
307         SpannableStringBuilder bootloaderStringBuilder = new SpannableStringBuilder(bootloaderLabel + bootloader);
308         SpannableStringBuilder androidStringBuilder = new SpannableStringBuilder(androidLabel + android);
309         SpannableStringBuilder buildStringBuilder = new SpannableStringBuilder(buildLabel + build);
310         SpannableStringBuilder webViewVersionStringBuilder = new SpannableStringBuilder(webViewVersionLabel + webView);
311         SpannableStringBuilder easyListStringBuilder = new SpannableStringBuilder(easyListLabel + blocklistVersions[0]);
312         SpannableStringBuilder easyPrivacyStringBuilder = new SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1]);
313         SpannableStringBuilder fanboyAnnoyanceStringBuilder = new SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2]);
314         SpannableStringBuilder fanboySocialStringBuilder = new SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3]);
315         SpannableStringBuilder ultraListStringBuilder = new SpannableStringBuilder(ultraListLabel + blocklistVersions[4]);
316         SpannableStringBuilder ultraPrivacyStringBuilder = new SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5]);
317
318         // Set the blue color span according to the theme.  The deprecated `getResources()` must be used until the minimum API >= 23.
319         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
320             blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
321         } else {
322             blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.violet_700));
323         }
324
325         // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
326         brandStringBuilder.setSpan(blueColorSpan, brandLabel.length(), brandStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
327         manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length(), manufacturerStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
328         modelStringBuilder.setSpan(blueColorSpan, modelLabel.length(), modelStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
329         deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length(), deviceStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
330         bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length(), bootloaderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
331         androidStringBuilder.setSpan(blueColorSpan, androidLabel.length(), androidStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
332         buildStringBuilder.setSpan(blueColorSpan, buildLabel.length(), buildStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
333         webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length(), webViewVersionStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
334         easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length(), easyListStringBuilder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
335         easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length(), easyPrivacyStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
336         fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length(), fanboyAnnoyanceStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
337         fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length(), fanboySocialStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
338         ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length(), ultraListStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
339         ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length(), ultraPrivacyStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
340
341         // Display the strings in the text boxes.
342         versionTextView.setText(version);
343         brandTextView.setText(brandStringBuilder);
344         manufacturerTextView.setText(manufacturerStringBuilder);
345         modelTextView.setText(modelStringBuilder);
346         deviceTextView.setText(deviceStringBuilder);
347         bootloaderTextView.setText(bootloaderStringBuilder);
348         androidTextView.setText(androidStringBuilder);
349         buildTextView.setText(buildStringBuilder);
350         webViewVersionTextView.setText(webViewVersionStringBuilder);
351         easyListTextView.setText(easyListStringBuilder);
352         easyPrivacyTextView.setText(easyPrivacyStringBuilder);
353         fanboyAnnoyanceTextView.setText(fanboyAnnoyanceStringBuilder);
354         fanboySocialTextView.setText(fanboySocialStringBuilder);
355         ultraListTextView.setText(ultraListStringBuilder);
356         ultraPrivacyTextView.setText(ultraPrivacyStringBuilder);
357
358         // Only populate the radio text view if there is a radio in the device.
359         // Null must be checked because some Samsung tablets report a null value for the radio instead of an empty string.  Grrrr.  <https://redmine.stoutner.com/issues/701>
360         if ((radio != null) && !radio.isEmpty()) {
361             String radioLabel = getString(R.string.radio) + "  ";
362             SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio);
363             radioStringBuilder.setSpan(blueColorSpan, radioLabel.length(), radioStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
364             radioTextView.setText(radioStringBuilder);
365         } else {  // This device does not have a radio.
366             radioTextView.setVisibility(View.GONE);
367         }
368
369         // Build.VERSION.SECURITY_PATCH is only available for SDK_INT >= 23.
370         if (Build.VERSION.SDK_INT >= 23) {
371             String securityPatchLabel = getString(R.string.security_patch) + "  ";
372             String securityPatch = Build.VERSION.SECURITY_PATCH;
373             SpannableStringBuilder securityPatchStringBuilder = new SpannableStringBuilder(securityPatchLabel + securityPatch);
374             securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length(), securityPatchStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
375             securityPatchTextView.setText(securityPatchStringBuilder);
376         } else {  // The API < 23.
377             // Hide the security patch text view.
378             securityPatchTextView.setVisibility(View.GONE);
379         }
380
381         // Only populate the WebView provider if the SDK >= 21.
382         if (Build.VERSION.SDK_INT >= 21) {
383             // Create the WebView provider label.
384             String webViewProviderLabel = getString(R.string.webview_provider) + "  ";
385
386             // Get the current WebView package info.
387             PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(context);
388
389             // Remove the warning below that the package info might be null.
390             assert webViewPackageInfo != null;
391
392             // Get the WebView provider name.
393             String webViewPackageName = webViewPackageInfo.packageName;
394
395             // Create the spannable string builder.
396             SpannableStringBuilder webViewProviderStringBuilder = new SpannableStringBuilder(webViewProviderLabel + webViewPackageName);
397
398             // Apply the coloration.
399             webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length(), webViewProviderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
400
401             // Display the WebView provider.
402             webViewProviderTextView.setText(webViewProviderStringBuilder);
403         } else {  // The API < 21.
404             // Hide the WebView provider text view.
405             webViewProviderTextView.setVisibility(View.GONE);
406         }
407
408         // Only populate the Orbot text view if it is installed.
409         if (!orbot.isEmpty()) {
410             String orbotLabel = getString(R.string.orbot) + "  ";
411             SpannableStringBuilder orbotStringBuilder = new SpannableStringBuilder(orbotLabel + orbot);
412             orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length(), orbotStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
413             orbotTextView.setText(orbotStringBuilder);
414         } else {  // Orbot is not installed.
415             orbotTextView.setVisibility(View.GONE);
416         }
417
418         // Only populate the I2P text view if it is installed.
419         if (!i2p.isEmpty()) {
420             String i2pLabel = getString(R.string.i2p)  + "  ";
421             SpannableStringBuilder i2pStringBuilder = new SpannableStringBuilder(i2pLabel + i2p);
422             i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length(), i2pStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
423             i2pTextView.setText(i2pStringBuilder);
424         } else {  // I2P is not installed.
425             i2pTextView.setVisibility(View.GONE);
426         }
427
428         // Only populate the OpenKeychain text view if it is installed.
429         if (!openKeychain.isEmpty()) {
430             String openKeychainLabel = getString(R.string.openkeychain) + "  ";
431             SpannableStringBuilder openKeychainStringBuilder = new SpannableStringBuilder(openKeychainLabel + openKeychain);
432             openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length(), openKeychainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
433             openKeychainTextView.setText(openKeychainStringBuilder);
434         } else {  //OpenKeychain is not installed.
435             openKeychainTextView.setVisibility(View.GONE);
436         }
437
438         // Display the package signature.
439         try {
440             // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
441             @SuppressLint("PackageManagerGetSignatures") Signature packageSignature = context.getPackageManager().getPackageInfo(context.getPackageName(),
442                     PackageManager.GET_SIGNATURES).signatures[0];
443
444             // Convert the signature to a byte array input stream.
445             InputStream certificateByteArrayInputStream = new ByteArrayInputStream(packageSignature.toByteArray());
446
447             // Display the certificate information on the screen.
448             try {
449                 // Instantiate a `CertificateFactory`.
450                 CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
451
452                 // Generate an `X509Certificate`.
453                 X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(certificateByteArrayInputStream);
454
455                 // Store the individual sections of the certificate that we are interested in.
456                 Principal issuerDNPrincipal = x509Certificate.getIssuerDN();
457                 Principal subjectDNPrincipal = x509Certificate.getSubjectDN();
458                 Date startDate = x509Certificate.getNotBefore();
459                 Date endDate = x509Certificate.getNotAfter();
460                 int certificateVersion = x509Certificate.getVersion();
461                 BigInteger serialNumberBigInteger = x509Certificate.getSerialNumber();
462                 String signatureAlgorithmNameString = x509Certificate.getSigAlgName();
463
464                 // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text.
465                 SpannableStringBuilder issuerDNStringBuilder = new SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString());
466                 SpannableStringBuilder subjectDNStringBuilder = new SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString());
467                 SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate));
468                 SpannableStringBuilder endDataStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate));
469                 SpannableStringBuilder certificateVersionStringBuilder = new SpannableStringBuilder(certificateVersionLabel + certificateVersion);
470                 SpannableStringBuilder serialNumberStringBuilder = new SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger);
471                 SpannableStringBuilder signatureAlgorithmStringBuilder = new SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString);
472
473                 // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
474                 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length(), issuerDNStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
475                 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length(), subjectDNStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
476                 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
477                 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDataStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
478                 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length(), certificateVersionStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
479                 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length(), serialNumberStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
480                 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length(), signatureAlgorithmStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
481
482                 // Display the strings in the text boxes.
483                 certificateIssuerDnTextView.setText(issuerDNStringBuilder);
484                 certificateSubjectDnTextView.setText(subjectDNStringBuilder);
485                 certificateStartDateTextView.setText(startDateStringBuilder);
486                 certificateEndDateTextView.setText(endDataStringBuilder);
487                 certificateVersionTextView.setText(certificateVersionStringBuilder);
488                 certificateSerialNumberTextView.setText(serialNumberStringBuilder);
489                 certificateSignatureAlgorithmTextView.setText(signatureAlgorithmStringBuilder);
490             } catch (CertificateException e) {
491                 // Do nothing if there is a certificate error.
492             }
493
494             // Get a handle for the runtime.
495             runtime = Runtime.getRuntime();
496
497             // Get a handle for the activity.
498             Activity activity = getActivity();
499
500             // Remove the incorrect lint warning below that the activity might be null.
501             assert activity != null;
502
503             // Get a handle for the activity manager.
504             activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
505
506             // Remove the incorrect lint warning below that the activity manager might be null.
507             assert activityManager != null;
508
509             // Instantiate a memory info variable.
510             memoryInfo = new ActivityManager.MemoryInfo();
511
512             // Define a number format.
513             numberFormat = NumberFormat.getInstance();
514
515             // Set the minimum and maximum number of fraction digits.
516             numberFormat.setMinimumFractionDigits(2);
517             numberFormat.setMaximumFractionDigits(2);
518
519             // Update the memory usage.
520             updateMemoryUsage(getActivity());
521         } catch (PackageManager.NameNotFoundException e) {
522             // Do nothing if `PackageManager` says Privacy Browser isn't installed.
523         }
524
525         // Scroll the tab if the saved instance state is not null.
526         if (savedInstanceState != null) {
527             aboutVersionLayout.post(() -> {
528                 aboutVersionLayout.setScrollX(savedInstanceState.getInt("scroll_x"));
529                 aboutVersionLayout.setScrollY(savedInstanceState.getInt("scroll_y"));
530             });
531         }
532
533         // Return the tab layout.
534         return aboutVersionLayout;
535     }
536
537     @Override
538     public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
539         // Inflate the about version menu.
540         menuInflater.inflate(R.menu.about_version_options_menu, menu);
541
542         // Run the default commands.
543         super.onCreateOptionsMenu(menu, menuInflater);
544     }
545
546     @Override
547     public boolean onOptionsItemSelected(@NonNull MenuItem menuItem) {
548         // Remove the incorrect lint warning below that the activity might be null.
549         assert getActivity() != null;
550
551         // Get the ID of the menu item that was selected.
552         int menuItemId = menuItem.getItemId();
553
554         // Run the appropriate commands.
555         if (menuItemId == R.id.copy) {  // Copy.
556             // Get the about version string.
557             String aboutVersionString = getAboutVersionString();
558
559             // Get a handle for the clipboard manager.
560             ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
561
562             // Remove the incorrect lint error below that the clipboard manager might be null.
563             assert clipboardManager != null;
564
565             // Save the about version string in a clip data.
566             ClipData aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString);
567
568             // Place the clip data on the clipboard.
569             clipboardManager.setPrimaryClip(aboutVersionClipData);
570
571             // Display a snackbar.
572             Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show();
573
574             // Consume the event.
575             return true;
576         } else if (menuItemId == R.id.share) {  // Share.
577             // Get the about version string.
578             String aboutString = getAboutVersionString();
579
580             // Create an email intent.
581             Intent emailIntent = new Intent(Intent.ACTION_SEND);
582
583             // Add the about version string to the intent.
584             emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString);
585
586             // Set the MIME type.
587             emailIntent.setType("text/plain");
588
589             // Set the intent to open in a new task.
590             emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
591
592             // Make it so.
593             startActivity(Intent.createChooser(emailIntent, getString(R.string.share)));
594
595             // Consume the event.
596             return true;
597         } else if (menuItemId == R.id.save_text) {  // Save text.
598             // Instantiate the save alert dialog.
599             DialogFragment saveTextDialogFragment = SaveDialog.save(SaveDialog.SAVE_ABOUT_VERSION_TEXT);
600
601             // Show the save alert dialog.
602             saveTextDialogFragment.show(getActivity().getSupportFragmentManager(), getString(R.string.save_dialog));
603
604             // Consume the event.
605             return true;
606         } else if (menuItemId == R.id.save_image) {  // Save image.
607             // Instantiate the save alert dialog.
608             DialogFragment saveImageDialogFragment = SaveDialog.save(SaveDialog.SAVE_ABOUT_VERSION_IMAGE);
609
610             // Show the save alert dialog.
611             saveImageDialogFragment.show(getActivity().getSupportFragmentManager(), getString(R.string.save_dialog));
612
613             // Consume the event.
614             return true;
615         } else {  // The home button was selected.
616             // Return the parent class.
617             return super.onOptionsItemSelected(menuItem);
618         }
619     }
620
621     @Override
622     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
623         // Run the default commands.
624         super.onSaveInstanceState(savedInstanceState);
625
626         // Save the scroll positions if the layout is not null, which can happen if a tab is not currently selected.
627         if (aboutVersionLayout != null) {
628             savedInstanceState.putInt("scroll_x", aboutVersionLayout.getScrollX());
629             savedInstanceState.putInt("scroll_y", aboutVersionLayout.getScrollY());
630         }
631     }
632
633     @Override
634     public void onPause() {
635         // Run the default commands.
636         super.onPause();
637
638         // Pause the updating of the memory usage.
639         updateMemoryUsageBoolean = false;
640     }
641
642     @Override
643     public void onResume() {
644         // Run the default commands.
645         super.onResume();
646
647         // Resume the updating of the memory usage.
648         updateMemoryUsageBoolean = true;
649     }
650
651     public void updateMemoryUsage(Activity activity) {
652         try {
653             // Update the memory usage if enabled.
654             if (updateMemoryUsageBoolean) {
655                 // Populate the memory info variable.
656                 activityManager.getMemoryInfo(memoryInfo);
657
658                 // Get the app memory information.
659                 long appAvailableMemoryLong = runtime.freeMemory();
660                 long appTotalMemoryLong = runtime.totalMemory();
661                 long appMaximumMemoryLong = runtime.maxMemory();
662
663                 // Calculate the app consumed memory.
664                 long appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong;
665
666                 // Get the system memory information.
667                 long systemTotalMemoryLong = memoryInfo.totalMem;
668                 long systemAvailableMemoryLong = memoryInfo.availMem;
669
670                 // Calculate the system consumed memory.
671                 long systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong;
672
673                 // Convert the memory information into mebibytes.
674                 float appConsumedMemoryFloat = (float) appConsumedMemoryLong / MEBIBYTE;
675                 float appAvailableMemoryFloat = (float) appAvailableMemoryLong / MEBIBYTE;
676                 float appTotalMemoryFloat = (float) appTotalMemoryLong / MEBIBYTE;
677                 float appMaximumMemoryFloat = (float) appMaximumMemoryLong / MEBIBYTE;
678                 float systemConsumedMemoryFloat = (float) systemConsumedMemoryLong / MEBIBYTE;
679                 float systemAvailableMemoryFloat = (float) systemAvailableMemoryLong / MEBIBYTE;
680                 float systemTotalMemoryFloat = (float) systemTotalMemoryLong / MEBIBYTE;
681
682                 // Get the mebibyte string.
683                 String mebibyte = getString(R.string.mebibyte);
684
685                 // Calculate the mebibyte length.
686                 int mebibyteLength = mebibyte.length();
687
688                 // Create spannable string builders.
689                 SpannableStringBuilder appConsumedMemoryStringBuilder = new SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat) + " " + mebibyte);
690                 SpannableStringBuilder appAvailableMemoryStringBuilder = new SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat) + " " + mebibyte);
691                 SpannableStringBuilder appTotalMemoryStringBuilder = new SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat) + " " + mebibyte);
692                 SpannableStringBuilder appMaximumMemoryStringBuilder = new SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat) + " " + mebibyte);
693                 SpannableStringBuilder systemConsumedMemoryStringBuilder = new SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat) + " " + mebibyte);
694                 SpannableStringBuilder systemAvailableMemoryStringBuilder = new SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat) + " " + mebibyte);
695                 SpannableStringBuilder systemTotalMemoryStringBuilder = new SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat) + " " + mebibyte);
696
697                 // Setup the spans to display the memory information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
698                 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length(), appConsumedMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
699                 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length(), appAvailableMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
700                 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length(), appTotalMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
701                 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length(), appMaximumMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
702                 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length(), systemConsumedMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
703                 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length(), systemAvailableMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
704                 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length(), systemTotalMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
705
706                 // Display the string in the text boxes.
707                 appConsumedMemoryTextView.setText(appConsumedMemoryStringBuilder);
708                 appAvailableMemoryTextView.setText(appAvailableMemoryStringBuilder);
709                 appTotalMemoryTextView.setText(appTotalMemoryStringBuilder);
710                 appMaximumMemoryTextView.setText(appMaximumMemoryStringBuilder);
711                 systemConsumedMemoryTextView.setText(systemConsumedMemoryStringBuilder);
712                 systemAvailableMemoryTextView.setText(systemAvailableMemoryStringBuilder);
713                 systemTotalMemoryTextView.setText(systemTotalMemoryStringBuilder);
714             }
715
716             // Schedule another memory update if the activity has not been destroyed.
717             if (!activity.isDestroyed()) {
718                 // Create a handler to update the memory usage.
719                 Handler updateMemoryUsageHandler = new Handler();
720
721                 // Create a runnable to update the memory usage.
722                 Runnable updateMemoryUsageRunnable = () -> updateMemoryUsage(activity);
723
724                 // Update the memory usage after 1000 milliseconds
725                 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000);
726             }
727         } catch (Exception exception) {
728             // Do nothing.
729         }
730     }
731
732     public String getAboutVersionString() {
733         // Initialize an about version string builder.
734         StringBuilder aboutVersionStringBuilder = new StringBuilder();
735
736         // Populate the about version string builder.
737         aboutVersionStringBuilder.append(privacyBrowserTextView.getText());
738         aboutVersionStringBuilder.append("\n");
739         aboutVersionStringBuilder.append(versionTextView.getText());
740         aboutVersionStringBuilder.append("\n\n");
741         aboutVersionStringBuilder.append(hardwareTextView.getText());
742         aboutVersionStringBuilder.append("\n");
743         aboutVersionStringBuilder.append(brandTextView.getText());
744         aboutVersionStringBuilder.append("\n");
745         aboutVersionStringBuilder.append(manufacturerTextView.getText());
746         aboutVersionStringBuilder.append("\n");
747         aboutVersionStringBuilder.append(modelTextView.getText());
748         aboutVersionStringBuilder.append("\n");
749         aboutVersionStringBuilder.append(deviceTextView.getText());
750         aboutVersionStringBuilder.append("\n");
751         aboutVersionStringBuilder.append(bootloaderTextView.getText());
752         aboutVersionStringBuilder.append("\n");
753         if (radioTextView.getVisibility() == View.VISIBLE) {
754             aboutVersionStringBuilder.append(radioTextView.getText());
755             aboutVersionStringBuilder.append("\n");
756         }
757         aboutVersionStringBuilder.append("\n");
758         aboutVersionStringBuilder.append(softwareTextView.getText());
759         aboutVersionStringBuilder.append("\n");
760         aboutVersionStringBuilder.append(androidTextView.getText());
761         aboutVersionStringBuilder.append("\n");
762         if (securityPatchTextView.getVisibility() == View.VISIBLE) {
763             aboutVersionStringBuilder.append(securityPatchTextView.getText());
764             aboutVersionStringBuilder.append("\n");
765         }
766         aboutVersionStringBuilder.append(buildTextView.getText());
767         aboutVersionStringBuilder.append("\n");
768         if (webViewProviderTextView.getVisibility() == View.VISIBLE) {
769             aboutVersionStringBuilder.append(webViewProviderTextView.getText());
770             aboutVersionStringBuilder.append("\n");
771         }
772         aboutVersionStringBuilder.append(webViewVersionTextView.getText());
773         aboutVersionStringBuilder.append("\n");
774         if (orbotTextView.getVisibility() == View.VISIBLE) {
775             aboutVersionStringBuilder.append(orbotTextView.getText());
776             aboutVersionStringBuilder.append("\n");
777         }
778         if (i2pTextView.getVisibility() == View.VISIBLE) {
779             aboutVersionStringBuilder.append(i2pTextView.getText());
780             aboutVersionStringBuilder.append("\n");
781         }
782         if (openKeychainTextView.getVisibility() == View.VISIBLE) {
783             aboutVersionStringBuilder.append(openKeychainTextView.getText());
784             aboutVersionStringBuilder.append("\n");
785         }
786         aboutVersionStringBuilder.append("\n");
787         aboutVersionStringBuilder.append(memoryUsageTextView.getText());
788         aboutVersionStringBuilder.append("\n");
789         aboutVersionStringBuilder.append(appConsumedMemoryTextView.getText());
790         aboutVersionStringBuilder.append("\n");
791         aboutVersionStringBuilder.append(appAvailableMemoryTextView.getText());
792         aboutVersionStringBuilder.append("\n");
793         aboutVersionStringBuilder.append(appTotalMemoryTextView.getText());
794         aboutVersionStringBuilder.append("\n");
795         aboutVersionStringBuilder.append(appMaximumMemoryTextView.getText());
796         aboutVersionStringBuilder.append("\n");
797         aboutVersionStringBuilder.append(systemConsumedMemoryTextView.getText());
798         aboutVersionStringBuilder.append("\n");
799         aboutVersionStringBuilder.append(systemAvailableMemoryTextView.getText());
800         aboutVersionStringBuilder.append("\n");
801         aboutVersionStringBuilder.append(systemTotalMemoryTextView.getText());
802         aboutVersionStringBuilder.append("\n\n");
803         aboutVersionStringBuilder.append(blocklistsTextView.getText());
804         aboutVersionStringBuilder.append("\n");
805         aboutVersionStringBuilder.append(easyListTextView.getText());
806         aboutVersionStringBuilder.append("\n");
807         aboutVersionStringBuilder.append(easyPrivacyTextView.getText());
808         aboutVersionStringBuilder.append("\n");
809         aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.getText());
810         aboutVersionStringBuilder.append("\n");
811         aboutVersionStringBuilder.append(fanboySocialTextView.getText());
812         aboutVersionStringBuilder.append("\n");
813         aboutVersionStringBuilder.append(ultraListTextView.getText());
814         aboutVersionStringBuilder.append("\n");
815         aboutVersionStringBuilder.append(ultraPrivacyTextView.getText());
816         aboutVersionStringBuilder.append("\n\n");
817         aboutVersionStringBuilder.append(packageSignatureTextView.getText());
818         aboutVersionStringBuilder.append("\n");
819         aboutVersionStringBuilder.append(certificateIssuerDnTextView.getText());
820         aboutVersionStringBuilder.append("\n");
821         aboutVersionStringBuilder.append(certificateSubjectDnTextView.getText());
822         aboutVersionStringBuilder.append("\n");
823         aboutVersionStringBuilder.append(certificateStartDateTextView.getText());
824         aboutVersionStringBuilder.append("\n");
825         aboutVersionStringBuilder.append(certificateEndDateTextView.getText());
826         aboutVersionStringBuilder.append("\n");
827         aboutVersionStringBuilder.append(certificateVersionTextView.getText());
828         aboutVersionStringBuilder.append("\n");
829         aboutVersionStringBuilder.append(certificateSerialNumberTextView.getText());
830         aboutVersionStringBuilder.append("\n");
831         aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.getText());
832
833         // Return the string.
834         return aboutVersionStringBuilder.toString();
835     }
836 }