18062c24230176ba9aa2b7d6f676fc2066b9809c
[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         if ((radio != null) && !radio.isEmpty()) {
360             String radioLabel = getString(R.string.radio) + "  ";
361             SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio);
362             radioStringBuilder.setSpan(blueColorSpan, radioLabel.length(), radioStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
363             radioTextView.setText(radioStringBuilder);
364         } else {  // This device does not have a radio.
365             radioTextView.setVisibility(View.GONE);
366         }
367
368         // Build.VERSION.SECURITY_PATCH is only available for SDK_INT >= 23.
369         if (Build.VERSION.SDK_INT >= 23) {
370             String securityPatchLabel = getString(R.string.security_patch) + "  ";
371             String securityPatch = Build.VERSION.SECURITY_PATCH;
372             SpannableStringBuilder securityPatchStringBuilder = new SpannableStringBuilder(securityPatchLabel + securityPatch);
373             securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length(), securityPatchStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
374             securityPatchTextView.setText(securityPatchStringBuilder);
375         } else {  // The API < 23.
376             // Hide the security patch text view.
377             securityPatchTextView.setVisibility(View.GONE);
378         }
379
380         // Only populate the WebView provider if the SDK >= 21.
381         if (Build.VERSION.SDK_INT >= 21) {
382             // Create the WebView provider label.
383             String webViewProviderLabel = getString(R.string.webview_provider) + "  ";
384
385             // Get the current WebView package info.
386             PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(context);
387
388             // Remove the warning below that the package info might be null.
389             assert webViewPackageInfo != null;
390
391             // Get the WebView provider name.
392             String webViewPackageName = webViewPackageInfo.packageName;
393
394             // Create the spannable string builder.
395             SpannableStringBuilder webViewProviderStringBuilder = new SpannableStringBuilder(webViewProviderLabel + webViewPackageName);
396
397             // Apply the coloration.
398             webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length(), webViewProviderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
399
400             // Display the WebView provider.
401             webViewProviderTextView.setText(webViewProviderStringBuilder);
402         } else {  // The API < 21.
403             // Hide the WebView provider text view.
404             webViewProviderTextView.setVisibility(View.GONE);
405         }
406
407         // Only populate the Orbot text view if it is installed.
408         if (!orbot.isEmpty()) {
409             String orbotLabel = getString(R.string.orbot) + "  ";
410             SpannableStringBuilder orbotStringBuilder = new SpannableStringBuilder(orbotLabel + orbot);
411             orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length(), orbotStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
412             orbotTextView.setText(orbotStringBuilder);
413         } else {  // Orbot is not installed.
414             orbotTextView.setVisibility(View.GONE);
415         }
416
417         // Only populate the I2P text view if it is installed.
418         if (!i2p.isEmpty()) {
419             String i2pLabel = getString(R.string.i2p)  + "  ";
420             SpannableStringBuilder i2pStringBuilder = new SpannableStringBuilder(i2pLabel + i2p);
421             i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length(), i2pStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
422             i2pTextView.setText(i2pStringBuilder);
423         } else {  // I2P is not installed.
424             i2pTextView.setVisibility(View.GONE);
425         }
426
427         // Only populate the OpenKeychain text view if it is installed.
428         if (!openKeychain.isEmpty()) {
429             String openKeychainLabel = getString(R.string.openkeychain) + "  ";
430             SpannableStringBuilder openKeychainStringBuilder = new SpannableStringBuilder(openKeychainLabel + openKeychain);
431             openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length(), openKeychainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
432             openKeychainTextView.setText(openKeychainStringBuilder);
433         } else {  //OpenKeychain is not installed.
434             openKeychainTextView.setVisibility(View.GONE);
435         }
436
437         // Display the package signature.
438         try {
439             // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
440             @SuppressLint("PackageManagerGetSignatures") Signature packageSignature = context.getPackageManager().getPackageInfo(context.getPackageName(),
441                     PackageManager.GET_SIGNATURES).signatures[0];
442
443             // Convert the signature to a byte array input stream.
444             InputStream certificateByteArrayInputStream = new ByteArrayInputStream(packageSignature.toByteArray());
445
446             // Display the certificate information on the screen.
447             try {
448                 // Instantiate a `CertificateFactory`.
449                 CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
450
451                 // Generate an `X509Certificate`.
452                 X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(certificateByteArrayInputStream);
453
454                 // Store the individual sections of the certificate that we are interested in.
455                 Principal issuerDNPrincipal = x509Certificate.getIssuerDN();
456                 Principal subjectDNPrincipal = x509Certificate.getSubjectDN();
457                 Date startDate = x509Certificate.getNotBefore();
458                 Date endDate = x509Certificate.getNotAfter();
459                 int certificateVersion = x509Certificate.getVersion();
460                 BigInteger serialNumberBigInteger = x509Certificate.getSerialNumber();
461                 String signatureAlgorithmNameString = x509Certificate.getSigAlgName();
462
463                 // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text.
464                 SpannableStringBuilder issuerDNStringBuilder = new SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString());
465                 SpannableStringBuilder subjectDNStringBuilder = new SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString());
466                 SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate));
467                 SpannableStringBuilder endDataStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate));
468                 SpannableStringBuilder certificateVersionStringBuilder = new SpannableStringBuilder(certificateVersionLabel + certificateVersion);
469                 SpannableStringBuilder serialNumberStringBuilder = new SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger);
470                 SpannableStringBuilder signatureAlgorithmStringBuilder = new SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString);
471
472                 // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
473                 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length(), issuerDNStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
474                 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length(), subjectDNStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
475                 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
476                 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDataStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
477                 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length(), certificateVersionStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
478                 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length(), serialNumberStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
479                 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length(), signatureAlgorithmStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
480
481                 // Display the strings in the text boxes.
482                 certificateIssuerDnTextView.setText(issuerDNStringBuilder);
483                 certificateSubjectDnTextView.setText(subjectDNStringBuilder);
484                 certificateStartDateTextView.setText(startDateStringBuilder);
485                 certificateEndDateTextView.setText(endDataStringBuilder);
486                 certificateVersionTextView.setText(certificateVersionStringBuilder);
487                 certificateSerialNumberTextView.setText(serialNumberStringBuilder);
488                 certificateSignatureAlgorithmTextView.setText(signatureAlgorithmStringBuilder);
489             } catch (CertificateException e) {
490                 // Do nothing if there is a certificate error.
491             }
492
493             // Get a handle for the runtime.
494             runtime = Runtime.getRuntime();
495
496             // Get a handle for the activity.
497             Activity activity = getActivity();
498
499             // Remove the incorrect lint warning below that the activity might be null.
500             assert activity != null;
501
502             // Get a handle for the activity manager.
503             activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
504
505             // Remove the incorrect lint warning below that the activity manager might be null.
506             assert activityManager != null;
507
508             // Instantiate a memory info variable.
509             memoryInfo = new ActivityManager.MemoryInfo();
510
511             // Define a number format.
512             numberFormat = NumberFormat.getInstance();
513
514             // Set the minimum and maximum number of fraction digits.
515             numberFormat.setMinimumFractionDigits(2);
516             numberFormat.setMaximumFractionDigits(2);
517
518             // Update the memory usage.
519             updateMemoryUsage(getActivity());
520         } catch (PackageManager.NameNotFoundException e) {
521             // Do nothing if `PackageManager` says Privacy Browser isn't installed.
522         }
523
524         // Scroll the tab if the saved instance state is not null.
525         if (savedInstanceState != null) {
526             aboutVersionLayout.post(() -> {
527                 aboutVersionLayout.setScrollX(savedInstanceState.getInt("scroll_x"));
528                 aboutVersionLayout.setScrollY(savedInstanceState.getInt("scroll_y"));
529             });
530         }
531
532         // Return the tab layout.
533         return aboutVersionLayout;
534     }
535
536     @Override
537     public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
538         // Inflate the about version menu.
539         menuInflater.inflate(R.menu.about_version_options_menu, menu);
540
541         // Run the default commands.
542         super.onCreateOptionsMenu(menu, menuInflater);
543     }
544
545     @Override
546     public boolean onOptionsItemSelected(@NonNull MenuItem menuItem) {
547         // Remove the incorrect lint warning below that the activity might be null.
548         assert getActivity() != null;
549
550         // Get the ID of the menu item that was selected.
551         int menuItemId = menuItem.getItemId();
552
553         // Run the appropriate commands.
554         if (menuItemId == R.id.copy) {  // Copy.
555             // Get the about version string.
556             String aboutVersionString = getAboutVersionString();
557
558             // Get a handle for the clipboard manager.
559             ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
560
561             // Remove the incorrect lint error below that the clipboard manager might be null.
562             assert clipboardManager != null;
563
564             // Save the about version string in a clip data.
565             ClipData aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString);
566
567             // Place the clip data on the clipboard.
568             clipboardManager.setPrimaryClip(aboutVersionClipData);
569
570             // Display a snackbar.
571             Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show();
572
573             // Consume the event.
574             return true;
575         } else if (menuItemId == R.id.share) {  // Share.
576             // Get the about version string.
577             String aboutString = getAboutVersionString();
578
579             // Create an email intent.
580             Intent emailIntent = new Intent(Intent.ACTION_SEND);
581
582             // Add the about version string to the intent.
583             emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString);
584
585             // Set the MIME type.
586             emailIntent.setType("text/plain");
587
588             // Set the intent to open in a new task.
589             emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
590
591             // Make it so.
592             startActivity(Intent.createChooser(emailIntent, getString(R.string.share)));
593
594             // Consume the event.
595             return true;
596         } else if (menuItemId == R.id.save_text) {  // Save text.
597             // Instantiate the save alert dialog.
598             DialogFragment saveTextDialogFragment = SaveDialog.save(SaveDialog.SAVE_ABOUT_VERSION_TEXT);
599
600             // Show the save alert dialog.
601             saveTextDialogFragment.show(getActivity().getSupportFragmentManager(), getString(R.string.save_dialog));
602
603             // Consume the event.
604             return true;
605         } else if (menuItemId == R.id.save_image) {  // Save image.
606             // Instantiate the save alert dialog.
607             DialogFragment saveImageDialogFragment = SaveDialog.save(SaveDialog.SAVE_ABOUT_VERSION_IMAGE);
608
609             // Show the save alert dialog.
610             saveImageDialogFragment.show(getActivity().getSupportFragmentManager(), getString(R.string.save_dialog));
611
612             // Consume the event.
613             return true;
614         } else {  // The home button was selected.
615             // Return the parent class.
616             return super.onOptionsItemSelected(menuItem);
617         }
618     }
619
620     @Override
621     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
622         // Run the default commands.
623         super.onSaveInstanceState(savedInstanceState);
624
625         // Save the scroll positions if the layout is not null, which can happen if a tab is not currently selected.
626         if (aboutVersionLayout != null) {
627             savedInstanceState.putInt("scroll_x", aboutVersionLayout.getScrollX());
628             savedInstanceState.putInt("scroll_y", aboutVersionLayout.getScrollY());
629         }
630     }
631
632     @Override
633     public void onPause() {
634         // Run the default commands.
635         super.onPause();
636
637         // Pause the updating of the memory usage.
638         updateMemoryUsageBoolean = false;
639     }
640
641     @Override
642     public void onResume() {
643         // Run the default commands.
644         super.onResume();
645
646         // Resume the updating of the memory usage.
647         updateMemoryUsageBoolean = true;
648     }
649
650     public void updateMemoryUsage(Activity activity) {
651         try {
652             // Update the memory usage if enabled.
653             if (updateMemoryUsageBoolean) {
654                 // Populate the memory info variable.
655                 activityManager.getMemoryInfo(memoryInfo);
656
657                 // Get the app memory information.
658                 long appAvailableMemoryLong = runtime.freeMemory();
659                 long appTotalMemoryLong = runtime.totalMemory();
660                 long appMaximumMemoryLong = runtime.maxMemory();
661
662                 // Calculate the app consumed memory.
663                 long appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong;
664
665                 // Get the system memory information.
666                 long systemTotalMemoryLong = memoryInfo.totalMem;
667                 long systemAvailableMemoryLong = memoryInfo.availMem;
668
669                 // Calculate the system consumed memory.
670                 long systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong;
671
672                 // Convert the memory information into mebibytes.
673                 float appConsumedMemoryFloat = (float) appConsumedMemoryLong / MEBIBYTE;
674                 float appAvailableMemoryFloat = (float) appAvailableMemoryLong / MEBIBYTE;
675                 float appTotalMemoryFloat = (float) appTotalMemoryLong / MEBIBYTE;
676                 float appMaximumMemoryFloat = (float) appMaximumMemoryLong / MEBIBYTE;
677                 float systemConsumedMemoryFloat = (float) systemConsumedMemoryLong / MEBIBYTE;
678                 float systemAvailableMemoryFloat = (float) systemAvailableMemoryLong / MEBIBYTE;
679                 float systemTotalMemoryFloat = (float) systemTotalMemoryLong / MEBIBYTE;
680
681                 // Get the mebibyte string.
682                 String mebibyte = getString(R.string.mebibyte);
683
684                 // Calculate the mebibyte length.
685                 int mebibyteLength = mebibyte.length();
686
687                 // Create spannable string builders.
688                 SpannableStringBuilder appConsumedMemoryStringBuilder = new SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat) + " " + mebibyte);
689                 SpannableStringBuilder appAvailableMemoryStringBuilder = new SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat) + " " + mebibyte);
690                 SpannableStringBuilder appTotalMemoryStringBuilder = new SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat) + " " + mebibyte);
691                 SpannableStringBuilder appMaximumMemoryStringBuilder = new SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat) + " " + mebibyte);
692                 SpannableStringBuilder systemConsumedMemoryStringBuilder = new SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat) + " " + mebibyte);
693                 SpannableStringBuilder systemAvailableMemoryStringBuilder = new SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat) + " " + mebibyte);
694                 SpannableStringBuilder systemTotalMemoryStringBuilder = new SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat) + " " + mebibyte);
695
696                 // Setup the spans to display the memory information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
697                 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length(), appConsumedMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
698                 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length(), appAvailableMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
699                 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length(), appTotalMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
700                 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length(), appMaximumMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
701                 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length(), systemConsumedMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
702                 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length(), systemAvailableMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
703                 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length(), systemTotalMemoryStringBuilder.length() - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
704
705                 // Display the string in the text boxes.
706                 appConsumedMemoryTextView.setText(appConsumedMemoryStringBuilder);
707                 appAvailableMemoryTextView.setText(appAvailableMemoryStringBuilder);
708                 appTotalMemoryTextView.setText(appTotalMemoryStringBuilder);
709                 appMaximumMemoryTextView.setText(appMaximumMemoryStringBuilder);
710                 systemConsumedMemoryTextView.setText(systemConsumedMemoryStringBuilder);
711                 systemAvailableMemoryTextView.setText(systemAvailableMemoryStringBuilder);
712                 systemTotalMemoryTextView.setText(systemTotalMemoryStringBuilder);
713             }
714
715             // Schedule another memory update if the activity has not been destroyed.
716             if (!activity.isDestroyed()) {
717                 // Create a handler to update the memory usage.
718                 Handler updateMemoryUsageHandler = new Handler();
719
720                 // Create a runnable to update the memory usage.
721                 Runnable updateMemoryUsageRunnable = () -> updateMemoryUsage(activity);
722
723                 // Update the memory usage after 1000 milliseconds
724                 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000);
725             }
726         } catch (Exception exception) {
727             // Do nothing.
728         }
729     }
730
731     public String getAboutVersionString() {
732         // Initialize an about version string builder.
733         StringBuilder aboutVersionStringBuilder = new StringBuilder();
734
735         // Populate the about version string builder.
736         aboutVersionStringBuilder.append(privacyBrowserTextView.getText());
737         aboutVersionStringBuilder.append("\n");
738         aboutVersionStringBuilder.append(versionTextView.getText());
739         aboutVersionStringBuilder.append("\n\n");
740         aboutVersionStringBuilder.append(hardwareTextView.getText());
741         aboutVersionStringBuilder.append("\n");
742         aboutVersionStringBuilder.append(brandTextView.getText());
743         aboutVersionStringBuilder.append("\n");
744         aboutVersionStringBuilder.append(manufacturerTextView.getText());
745         aboutVersionStringBuilder.append("\n");
746         aboutVersionStringBuilder.append(modelTextView.getText());
747         aboutVersionStringBuilder.append("\n");
748         aboutVersionStringBuilder.append(deviceTextView.getText());
749         aboutVersionStringBuilder.append("\n");
750         aboutVersionStringBuilder.append(bootloaderTextView.getText());
751         aboutVersionStringBuilder.append("\n");
752         if (radioTextView.getVisibility() == View.VISIBLE) {
753             aboutVersionStringBuilder.append(radioTextView.getText());
754             aboutVersionStringBuilder.append("\n");
755         }
756         aboutVersionStringBuilder.append("\n");
757         aboutVersionStringBuilder.append(softwareTextView.getText());
758         aboutVersionStringBuilder.append("\n");
759         aboutVersionStringBuilder.append(androidTextView.getText());
760         aboutVersionStringBuilder.append("\n");
761         if (securityPatchTextView.getVisibility() == View.VISIBLE) {
762             aboutVersionStringBuilder.append(securityPatchTextView.getText());
763             aboutVersionStringBuilder.append("\n");
764         }
765         aboutVersionStringBuilder.append(buildTextView.getText());
766         aboutVersionStringBuilder.append("\n");
767         if (webViewProviderTextView.getVisibility() == View.VISIBLE) {
768             aboutVersionStringBuilder.append(webViewProviderTextView.getText());
769             aboutVersionStringBuilder.append("\n");
770         }
771         aboutVersionStringBuilder.append(webViewVersionTextView.getText());
772         aboutVersionStringBuilder.append("\n");
773         if (orbotTextView.getVisibility() == View.VISIBLE) {
774             aboutVersionStringBuilder.append(orbotTextView.getText());
775             aboutVersionStringBuilder.append("\n");
776         }
777         if (i2pTextView.getVisibility() == View.VISIBLE) {
778             aboutVersionStringBuilder.append(i2pTextView.getText());
779             aboutVersionStringBuilder.append("\n");
780         }
781         if (openKeychainTextView.getVisibility() == View.VISIBLE) {
782             aboutVersionStringBuilder.append(openKeychainTextView.getText());
783             aboutVersionStringBuilder.append("\n");
784         }
785         aboutVersionStringBuilder.append("\n");
786         aboutVersionStringBuilder.append(memoryUsageTextView.getText());
787         aboutVersionStringBuilder.append("\n");
788         aboutVersionStringBuilder.append(appConsumedMemoryTextView.getText());
789         aboutVersionStringBuilder.append("\n");
790         aboutVersionStringBuilder.append(appAvailableMemoryTextView.getText());
791         aboutVersionStringBuilder.append("\n");
792         aboutVersionStringBuilder.append(appTotalMemoryTextView.getText());
793         aboutVersionStringBuilder.append("\n");
794         aboutVersionStringBuilder.append(appMaximumMemoryTextView.getText());
795         aboutVersionStringBuilder.append("\n");
796         aboutVersionStringBuilder.append(systemConsumedMemoryTextView.getText());
797         aboutVersionStringBuilder.append("\n");
798         aboutVersionStringBuilder.append(systemAvailableMemoryTextView.getText());
799         aboutVersionStringBuilder.append("\n");
800         aboutVersionStringBuilder.append(systemTotalMemoryTextView.getText());
801         aboutVersionStringBuilder.append("\n\n");
802         aboutVersionStringBuilder.append(blocklistsTextView.getText());
803         aboutVersionStringBuilder.append("\n");
804         aboutVersionStringBuilder.append(easyListTextView.getText());
805         aboutVersionStringBuilder.append("\n");
806         aboutVersionStringBuilder.append(easyPrivacyTextView.getText());
807         aboutVersionStringBuilder.append("\n");
808         aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.getText());
809         aboutVersionStringBuilder.append("\n");
810         aboutVersionStringBuilder.append(fanboySocialTextView.getText());
811         aboutVersionStringBuilder.append("\n");
812         aboutVersionStringBuilder.append(ultraListTextView.getText());
813         aboutVersionStringBuilder.append("\n");
814         aboutVersionStringBuilder.append(ultraPrivacyTextView.getText());
815         aboutVersionStringBuilder.append("\n\n");
816         aboutVersionStringBuilder.append(packageSignatureTextView.getText());
817         aboutVersionStringBuilder.append("\n");
818         aboutVersionStringBuilder.append(certificateIssuerDnTextView.getText());
819         aboutVersionStringBuilder.append("\n");
820         aboutVersionStringBuilder.append(certificateSubjectDnTextView.getText());
821         aboutVersionStringBuilder.append("\n");
822         aboutVersionStringBuilder.append(certificateStartDateTextView.getText());
823         aboutVersionStringBuilder.append("\n");
824         aboutVersionStringBuilder.append(certificateEndDateTextView.getText());
825         aboutVersionStringBuilder.append("\n");
826         aboutVersionStringBuilder.append(certificateVersionTextView.getText());
827         aboutVersionStringBuilder.append("\n");
828         aboutVersionStringBuilder.append(certificateSerialNumberTextView.getText());
829         aboutVersionStringBuilder.append("\n");
830         aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.getText());
831
832         // Return the string.
833         return aboutVersionStringBuilder.toString();
834     }
835 }