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