apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
release {
minifyEnabled true
shrinkResources true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
implementation 'androidx.webkit:webkit:1.3.0'
// Include the Kotlin standard libraries. This should be the same version number listed in project build.gradle.
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.20"
// Include the Google material library.
implementation 'com.google.android.material:material:1.2.1'
+# Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+#
+# This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+#
+# Privacy Browser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Privacy Browser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
+
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
-# in /home/soren/Android/Sdk/tools/proguard/proguard-android.txt
+# in ~/Android/Sdk/tools/proguard/proguard-android-optimize.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
+
+# Make Kotlin ViewModels work correctly.
+-keep class * extends androidx.lifecycle.ViewModel { *; }
\ No newline at end of file
--- /dev/null
+<!--
+ Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+
+ This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+ Privacy Browser is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Privacy Browser is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>. -->
+
+<html>
+ <head>
+ <meta charset="UTF-8">
+
+ <link rel="stylesheet" href="../css/theme.css">
+
+ <!-- Setting the color scheme instructs the WebView to respect `prefers-color-scheme` @media CSS. -->
+ <meta name="color-scheme" content="light dark">
+ </head>
+
+ <body>
+ <h3>Have full network access</h3>
+ <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#INTERNET">android.permission.INTERNET</a></p>
+ <p>Required for the WebView to access the internet. Without this permission, Privacy Browser would be “No Browser: Protecting Your Privacy by Staying Completely Off the Internet”.</p>
+
+ <h3>Install shortcuts</h3>
+ <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#INSTALL_SHORTCUT">com.android.launcher.permission.INSTALL_SHORTCUT</a></p>
+ <p>Required to add shortcuts for websites to the launcher desktop.</p>
+
+ <h3>Read storage</h3>
+ <p><a href="https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE">android.permission.READ_EXTERNAL_STORAGE</a></p>
+ <p>Required to import settings from public folders. On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can import settings from the app’s folders instead.</p>
+
+ <h3>Write storage</h3>
+ <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE">android.permission.WRITE_EXTERNAL_STORAGE</a></p>
+ <p>Required to export settings and download files to the public folders.
+ On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can export settings and store downloads in the app’s folders instead.</p>
+
+ <br/>
+ <hr/>
+ <br/>
+
+ <p>In addition, Privacy Browser Free displays ads from Google’s AdMob network using the Firebase backend.
+ For the free flavor, Firebase adds the following permissions even though they are not listed in the source code
+ <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest file</a>.</p>
+
+ <h3>View network connections</h3>
+ <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_NETWORK_STATE">android.permission.ACCESS_NETWORK_STATE</a></p>
+ <p>Allows the ads to tell when you are connected to the internet and when you aren’t (presumably so they don’t try to reload an ad when you are disconnected).
+ They can also tell if you are connected via Wi-Fi, 2G, 3G, 4G, etc.</p>
+
+ <h3>Prevent phone from sleeping</h3>
+ <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#WAKE_LOCK">android.permission.WAKE_LOCK</a></p>
+ <p>Allows the ads to keep the processor from sleeping and the screen from dimming, although in my testing I don’t think the ads actually do this.</p>
+
+ <h3>Play Install Referrer API</h3>
+ <p><a href="https://android-developers.googleblog.com/2017/11/google-play-referrer-api-track-and.html">com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE</a></p>
+ <p>Allows other apps to tell if their installation was launched from an ad in Privacy Browser Free.</p>
+
+ <h3>Receive data from Internet</h3>
+ <p><a href="http://androidpermissions.com/permission/com.google.android.c2dm.permission.RECEIVE">com.google.android.c2dm.permission.RECEIVE</a></p>
+ <p>Allows Google to send information directly to the AdView without having to receive a request first (cloud-to-device messaging).</p>
+ </body>
+</html>
\ No newline at end of file
--- /dev/null
+<!--
+ Copyright © 2016-2018,2020 Soren Stoutner <soren@stoutner.com>.
+
+ This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+ Privacy Browser is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Privacy Browser is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>. -->
+
+<html>
+ <head>
+ <meta charset="UTF-8">
+
+ <link rel="stylesheet" href="../css/theme.css">
+
+ <!-- Setting the color scheme instructs the WebView to respect `prefers-color-scheme` @media CSS. -->
+ <meta name="color-scheme" content="light dark">
+ </head>
+
+ <body>
+ <h3>Privacy Browser Free</h3>
+ <p><strong class="red">Privacy Browser Free does not collect any user information.</strong></p>
+
+
+ <h3>Google Play</h3>
+ <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
+ Google provides <em>anonymized summary installation information</em> to developers, including the number of installs organized by the following categories.</p>
+ <ul>
+ <li><item>Android version</item> (eg. Android 7.1)</li>
+ <li><item>Device</item> (eg. Samsung Galaxy S6 [zeroflte])</li>
+ <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
+ <li><item>Country</item> (eg. United States)</li>
+ <li><item>Language</item> (eg. English [United States])</li>
+ <li><item>App version</item> (eg. 14)</li>
+ <li><item>Carrier</item> (eg. T-Mobile - US)</li>
+ </ul>
+
+
+ <h3>Google Play Ratings</h3>
+ <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
+ Google provides developers with <em>anonymized summaries</em> of the following information related to user ratings.</p>
+ <ul>
+ <li><item>Country</item> (eg. United States)</li>
+ <li><item>Language</item> (eg. English)</li>
+ <li><item>App version</item> (eg. 14)</li>
+ <li><item>Android version</item> (eg. Android 7.1)</li>
+ <li><item>Device</item> (eg. Google Nexus 5X [bullhead])</li>
+ <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
+ </ul>
+
+
+ <h3>Google Play Reviews</h3>
+ <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
+ In addition to the name of the reviewer, the rating, and the text of the review (which are all available publicly), Google provides some or all of the following information to the developer.</p>
+ <ul>
+ <li><item>Version code</item> (eg. 7)</li>
+ <li><item>Version name</item> (eg. 1.6)</li>
+ <li><item>Android version</item> (eg. Android 5.1)</li>
+ <li><item>Device</item> (eg. Galaxy S6 Edge+ [zenlte])</li>
+ <li><item>Manufacturer</item> (eg. Samsung)</li>
+ <li><item>Device type</item> (eg. Phone)</li>
+ <li><item>CPU make</item> (eg. Samsung)</li>
+ <li><item>CPU model</item> (eg. Exynos 7420)</li>
+ <li><item>Screen density</item> (eg. 560 dpi)</li>
+ <li><item>Screen size</item> (eg. 2560 x 1440)</li>
+ <li><item>RAM</item> (eg. 4096 MB)</li>
+ <li><item>Native platform</item> (eg. armeabi-v7a,armeabi,arm64v8a)</li>
+ <li><item>OpenGL ES version</item> (eg. 3.1)</li>
+ <li><item>Device language</item> (eg. English)</li>
+ </ul>
+
+
+ <h3>Advertisements</h3>
+ <p>Privacy Browser Free displays a banner ad across the bottom of the screen using Google's AdMob network,
+ which has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
+ These ads are set to be <a href="https://developers.google.com/admob/android/eu-consent#update_consent_status">non-personalized</a>.
+ AdMob reports <em>anonymized summaries</em> of the following information to developers.</p>
+ <ul>
+ <li><item>Total impressions</item></li>
+ <li><item>Total clicks</item></li>
+ <li><item>Platforms</item> (eg. high-end mobile devices, tablets)</li>
+ <li><item>Activity by country</item></li>
+ </ul>
+
+
+ <h3>Direct Communications</h3>
+ <p>Users may choose to send direct communications to Stoutner, like email messages and comments on <a href="https://www.stoutner.com/">stoutner.com</a>.</p>
+
+
+ <h3>Use of Information</h3>
+ <p><strong class="blue">Stoutner may use this information to assist in the development of Privacy Browser and communicate the status of the project to users.</strong>
+ <strong class="red">Stoutner will never sell this information nor transfer it to any third party that would use it for advertising or marketing.</strong></p>
+
+ <hr />
+ <p style="text-align: center;"><em>Revision 1.7, 14 May 2019</em></p>
+ </body>
+</html>
\ No newline at end of file
<resources>
<!-- Activities. -->
+ <string name="privacy_browser">Privacy Browser Gratuito</string>
<!-- Create Home Screen Shortcut Alert Dialog. -->
+ <string name="open_with_privacy_browser">Abrir com Privacy Browser Gratuito.</string>
<!-- Ad Consent. -->
+ <string name="ad_consent_text">Privacy Browser Gratuito exibe um anúncio de banner na parte inferior da tela.
+ Esses anúncios vêm do conjunto de provedores comumente usados do Google e são configurados para não serem personalizados.
+ \n\nA versão padrão do Privacy Browser não contém anúncios de aplicativos.</string>
+ <string name="close_browser">Fechar Navegador</string>
+ <string name="accept_ads">Aceitar Anúncios</string>
</resources>
\ No newline at end of file
</head>
<body>
- <h3>3.6 (version code 52)</h3>
- <p>16. November 2020 - Mindest-API 19, Ziel-API 29</p>
+ <h3><a href="https://www.stoutner.com/privacy-browser-3-6/">3.6</a> (version code 52)</h3>
+ <p><a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=8a06558b0071fa94e2a7d1093b3118417ac5cc8f">16. November 2020</a> - Mindest-API 19, Ziel-API 29</p>
<ul>
<li>Fix eines Bugs beim gepufferten Abspielen von Audio <a href="https://redmine.stoutner.com/issues/595">nach dem Schließen eines Tabs</a>.</li>
<li>Möglichkeit, unter Android 7 (API 24) und neuer <a href="https://redmine.stoutner.com/issues/636">benutzerdfinierten Zertifikatsstellen zu vertrauen</a>.</li>
</head>
<body>
- <h3>3.6 (version code 52)</h3>
- <p>16 November 2020 - minimum API 19, target API 29</p>
+ <h3><a href="https://www.stoutner.com/privacy-browser-3-6/">3.6</a> (version code 52)</h3>
+ <p><a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=8a06558b0071fa94e2a7d1093b3118417ac5cc8f">16 November 2020</a> - minimum API 19, target API 29</p>
<ul>
<li>Fix buffered audio playing <a href="https://redmine.stoutner.com/issues/595">after a tab is closed</a>.</li>
<li>Trust <a href="https://redmine.stoutner.com/issues/636">user certificate authorities</a> on Android 7 (API 24) and newer.</li>
</head>
<body>
- <h3>3.6 (código de versión 52)</h3>
- <p>16 de noviembre de 2020 - API mínimo 19, API dirigido 29</p>
+ <h3><a href="https://www.stoutner.com/privacy-browser-3-6/">3.6</a> (código de versión 52)</h3>
+ <p><a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=8a06558b0071fa94e2a7d1093b3118417ac5cc8f">16 de noviembre de 2020</a> - API mínimo 19, API dirigido 29</p>
<ul>
<li>Arreglar la reproducción de audio con buffer <a href="https://redmine.stoutner.com/issues/595">después de cerrar una pestaña</a>.</li>
<li>Confiar en <a href="https://redmine.stoutner.com/issues/636">las autoridades de certificación de usuarios</a> de Android 7 (API 24) o más reciente.</li>
</head>
<body>
- <h3>3.6 (version du code 52)</h3>
- <p>16 Novembre 2020 - API minimale : 19, API optimale : 29</p>
+ <h3><a href="https://www.stoutner.com/privacy-browser-3-6/">3.6</a> (version du code 52)</h3>
+ <p><a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=8a06558b0071fa94e2a7d1093b3118417ac5cc8f">16 Novembre 2020</a> - API minimale : 19, API optimale : 29</p>
<ul>
<li>Correction de la lecture audio en mémoire tampon <a href="https://redmine.stoutner.com/issues/595">après la fermeture d'un onglet</a>.</li>
<li>Confiance aux <a href="https://redmine.stoutner.com/issues/636">autorités de certification de l'utilisateur</a> sur Android 7 (API 24) et suivants.</li>
</head>
<body>
- <h3>3.6 (versione codice 52)</h3>
- <p>16 Novembre 2020 - minima API 19, target API 29</p>
+ <h3><a href="https://www.stoutner.com/privacy-browser-3-6/">3.6</a> (versione codice 52)</h3>
+ <p><a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=8a06558b0071fa94e2a7d1093b3118417ac5cc8f">16 Novembre 2020</a> - minima API 19, target API 29</p>
<ul>
<li>Sistemazione della riproduzione audio nel buffer <a href="https://redmine.stoutner.com/issues/595">dopo la chiusura di una scheda</a>.</li>
<li>Accettazione delle <a href="https://redmine.stoutner.com/issues/636">autorità dei certificati degli utenti</a> su Android 7 (API 24) e successivi.</li>
</head>
<body>
- <h3>3.6 (código da versão 52)</h3>
- <p>16 November 2020 - minimum API 19, target API 29</p>
+ <h3><a href="https://www.stoutner.com/privacy-browser-3-6/">3.6</a> (código da versão 52)</h3>
+ <p><a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=8a06558b0071fa94e2a7d1093b3118417ac5cc8f">16 November 2020</a> - minimum API 19, target API 29</p>
<ul>
<li>Correção da reprodução do áudio em buffer <a href="https://redmine.stoutner.com/issues/595">após o fechamento de uma guia</a>.</li>
<li>Confiar <a href="https://redmine.stoutner.com/issues/636">nas autoridades de certificação do usuário</a> no Android 7 (API 24) e mais recente.</li>
<a href="https://redmine.stoutner.com/issues/618">user</a> <a href="https://redmine.stoutner.com/issues/609">experiência</a> <a href="https://redmine.stoutner.com/issues/592">e</a>
<a href="https://redmine.stoutner.com/issues/567">gráfica</a> <a href="https://redmine.stoutner.com/issues/554">interface</a>.</li>
<li>Tradução do português brasileiro fornecida por <a href="mailto:mochileiro2006-trilhas@yahoo.com.br">Thiago Nazareno Conceição Silva de Jesus</a>.</li>
- <li>Updated French translation provided by <a href="mailto:kevinliste@framalistes.org">Kévin LE FLOHIC</a>.</li>
- <li>Updated German translation provided by Bernhard G. Keller.</li>
- <li>Updated Italian translation provided by Francesco Buratti.</li>
- <li>Updated Russian translation.</li>
- <li>Updated Spanish translation provided by Jose A. León.</li>
+ <li>Tradução francesa atualizada fornecida por <a href="mailto:kevinliste@framalistes.org">Kévin LE FLOHIC</a>.</li>
+ <li>Tradução alemã atualizada fornecida por Bernhard G. Keller.</li>
+ <li>Tradução italiana atualizada fornecida por Francesco Buratti.</li>
+ <li>Tradução russa atualizada.</li>
+ <li>Tradução em espanhol atualizada fornecida por Jose A. León.</li>
</ul>
<h3><a href="https://www.stoutner.com/privacy-browser-3-5-1/">3.5.1</a> (código da versão 51)</h3>
<!--
Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
<!--
Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
<!--
Copyright © 2016-2018,2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
<!--
Copyright © 2016-2018,2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
<!--
Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
<!--
Copyright © 2018,2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
<!--
Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
<!--
Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
<!--
Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ Translation 2020 Thiago Nazareno Conceição Silva de Jesus <mochileiro2006-trilhas@yahoo.com.br>. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
Privacy Browser is free software: you can redistribute it and/or modify
</head>
<body>
- <h3>3.6 (код версии 52)</h3>
- <p>16 Ноябрь 2020 года - минимальный API 19, целевой API 29</p>
+ <h3><a href="https://www.stoutner.com/privacy-browser-3-6/">3.6</a> (код версии 52)</h3>
+ <p><a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=8a06558b0071fa94e2a7d1093b3118417ac5cc8f">16 Ноябрь 2020 года</a> - минимальный API 19, целевой API 29</p>
<ul>
<li>Исправлено воспроизведение буферизованного звука <a href="https://redmine.stoutner.com/issues/595">после закрытия вкладки</a>.</li>
<li>Доверие <a href="https://redmine.stoutner.com/issues/636">пользовательским сертификатам авторизации</a> на Android 7 (API 24) и новее.</li>
</head>
<body>
- <h3>3.6 (version code 52)</h3>
- <p>16 November 2020 - minimum API 19, target API 29</p>
+ <h3><a href="https://www.stoutner.com/privacy-browser-3-6/">3.6</a> (version code 52)</h3>
+ <p><a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=8a06558b0071fa94e2a7d1093b3118417ac5cc8f">16 November 2020</a> - minimum API 19, target API 29</p>
<ul>
<li>Fix buffered audio playing <a href="https://redmine.stoutner.com/issues/595">after a tab is closed</a>.</li>
<li>Trust <a href="https://redmine.stoutner.com/issues/636">user certificate authorities</a> on Android 7 (API 24) and newer.</li>
Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
// Add the variables to the intent.
- viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
- viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
+ viewSourceIntent.putExtra(ViewSourceActivityKt.CURRENT_URL, currentWebView.getUrl());
+ viewSourceIntent.putExtra(ViewSourceActivityKt.USER_AGENT, currentWebView.getSettings().getUserAgentString());
// Make it so.
startActivity(viewSourceIntent);
+++ /dev/null
-/*
- * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.activities;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.LocaleList;
-import android.preference.PreferenceManager;
-import android.text.Spanned;
-import android.text.style.ForegroundColorSpan;
-import android.util.TypedValue;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-import androidx.core.app.NavUtils;
-import androidx.fragment.app.DialogFragment;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-
-import com.google.android.material.snackbar.Snackbar;
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.dialogs.AboutViewSourceDialog;
-import com.stoutner.privacybrowser.helpers.ProxyHelper;
-import com.stoutner.privacybrowser.viewmodelfactories.WebViewSourceFactory;
-import com.stoutner.privacybrowser.viewmodels.WebViewSource;
-
-import java.net.Proxy;
-import java.util.Locale;
-
-public class ViewSourceActivity extends AppCompatActivity {
- // `activity` is used in `onCreate()` and `goBack()`.
- private Activity activity;
-
- // The color spans are used in `onCreate()` and `highlightUrlText()`.
- private ForegroundColorSpan redColorSpan;
- private ForegroundColorSpan initialGrayColorSpan;
- private ForegroundColorSpan finalGrayColorSpan;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // Get a handle for the shared preferences.
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
-
- // Get the screenshot preference.
- boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-
- // Disable screenshots if not allowed.
- if (!allowScreenshots) {
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
- }
-
- // Set the theme.
- setTheme(R.style.PrivacyBrowser);
-
- // Run the default commands.
- super.onCreate(savedInstanceState);
-
- // Get the launching intent
- Intent intent = getIntent();
-
- // Get the information from the intent.
- String userAgent = intent.getStringExtra("user_agent");
- String currentUrl = intent.getStringExtra("current_url");
-
- // Remove the incorrect lint warning below that the user agent might be null.
- assert userAgent != null;
-
- // Store a handle for the current activity.
- activity = this;
-
- // Set the content view.
- setContentView(R.layout.view_source_coordinatorlayout);
-
- // Get a handle for the toolbar.
- Toolbar toolbar = findViewById(R.id.view_source_toolbar);
-
- // Set the support action bar.
- setSupportActionBar(toolbar);
-
- // Get a handle for the action bar.
- final ActionBar actionBar = getSupportActionBar();
-
- // Remove the incorrect lint warning that the action bar might be null.
- assert actionBar != null;
-
- // Add the custom layout to the action bar.
- actionBar.setCustomView(R.layout.view_source_app_bar);
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
-
- // Get handles for the views.
- EditText urlEditText = findViewById(R.id.url_edittext);
- TextView requestHeadersTextView = findViewById(R.id.request_headers);
- TextView responseMessageTextView = findViewById(R.id.response_message);
- TextView responseHeadersTextView = findViewById(R.id.response_headers);
- TextView responseBodyTextView = findViewById(R.id.response_body);
- ProgressBar progressBar = findViewById(R.id.progress_bar);
- SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.view_source_swiperefreshlayout);
-
- // Populate the URL text box.
- urlEditText.setText(currentUrl);
-
- // Initialize the gray foreground color spans for highlighting the URLs. The deprecated `getResources()` must be used until API >= 23.
- initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
- finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
-
- // Get the current theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
- // Set the red color span according to the theme.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
- } else {
- redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
- }
-
- // Apply text highlighting to the URL.
- highlightUrlText();
-
- // Get a handle for the input method manager, which is used to hide the keyboard.
- InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
-
- // Remove the lint warning that the input method manager might be null.
- assert inputMethodManager != null;
-
- // Remove the formatting from the URL when the user is editing the text.
- urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
- if (hasFocus) { // The user is editing `urlTextBox`.
- // Remove the highlighting.
- urlEditText.getText().removeSpan(redColorSpan);
- urlEditText.getText().removeSpan(initialGrayColorSpan);
- urlEditText.getText().removeSpan(finalGrayColorSpan);
- } else { // The user has stopped editing `urlTextBox`.
- // Hide the soft keyboard.
- inputMethodManager.hideSoftInputFromWindow(urlEditText.getWindowToken(), 0);
-
- // Move to the beginning of the string.
- urlEditText.setSelection(0);
-
- // Reapply the highlighting.
- highlightUrlText();
- }
- });
-
- // Set the refresh color scheme according to the theme.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
- } else {
- swipeRefreshLayout.setColorSchemeResources(R.color.violet_500);
- }
-
- // Initialize a color background typed value.
- TypedValue colorBackgroundTypedValue = new TypedValue();
-
- // Get the color background from the theme.
- getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
-
- // Get the color background int from the typed value.
- int colorBackgroundInt = colorBackgroundTypedValue.data;
-
- // Set the swipe refresh background color.
- swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
-
- // Get the Do Not Track status.
- boolean doNotTrack = sharedPreferences.getBoolean("do_not_track", false);
-
- // Instantiate a locale string.
- String localeString;
-
- // Populate the locale string.
- if (Build.VERSION.SDK_INT >= 24) { // SDK >= 24 has a list of locales.
- // Get the list of locales.
- LocaleList localeList = getResources().getConfiguration().getLocales();
-
- // Initialize a string builder to extract the locales from the list.
- StringBuilder localesStringBuilder = new StringBuilder();
-
- // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
- int q = 10;
-
- // Populate the string builder with the contents of the locales list.
- for (int i = 0; i < localeList.size(); i++) {
- // Append a comma if there is already an item in the string builder.
- if (i > 0) {
- localesStringBuilder.append(",");
- }
-
- // Get the locale from the list.
- Locale locale = localeList.get(i);
-
- // Add the locale to the string. `locale` by default displays as `en_US`, but WebView uses the `en-US` format.
- localesStringBuilder.append(locale.getLanguage());
- localesStringBuilder.append("-");
- localesStringBuilder.append(locale.getCountry());
-
- // If not the first locale, append `;q=0.x`, which drops by .1 for each removal from the main locale until q=0.1.
- if (q < 10) {
- localesStringBuilder.append(";q=0.");
- localesStringBuilder.append(q);
- }
-
- // Decrement `q` if it is greater than 1.
- if (q > 1) {
- q--;
- }
-
- // Add a second entry for the language only portion of the locale.
- localesStringBuilder.append(",");
- localesStringBuilder.append(locale.getLanguage());
-
- // Append `1;q=0.x`, which drops by .1 for each removal form the main locale until q=0.1.
- localesStringBuilder.append(";q=0.");
- localesStringBuilder.append(q);
-
- // Decrement `q` if it is greater than 1.
- if (q > 1) {
- q--;
- }
- }
-
- // Store the populated string builder in the locale string.
- localeString = localesStringBuilder.toString();
- } else { // SDK < 24 only has a primary locale.
- // Store the locale in the locale string.
- localeString = Locale.getDefault().toString();
- }
-
- // Instantiate the proxy helper.
- ProxyHelper proxyHelper = new ProxyHelper();
-
- // Get the current proxy.
- Proxy proxy = proxyHelper.getCurrentProxy(this);
-
- // Make the progress bar visible.
- progressBar.setVisibility(View.VISIBLE);
-
- // Set the progress bar to be indeterminate.
- progressBar.setIndeterminate(true);
-
- // Instantiate the WebView source factory.
- ViewModelProvider.Factory webViewSourceFactory = new WebViewSourceFactory(currentUrl, userAgent, doNotTrack, localeString, proxy, MainWebViewActivity.executorService);
-
- // Instantiate the WebView source view model class.
- final WebViewSource webViewSource = new ViewModelProvider(this, webViewSourceFactory).get(WebViewSource.class);
-
- // Create a source observer.
- webViewSource.observeSource().observe(this, sourceStringArray -> {
- // Populate the text views. This can take a long time, and freezes the user interface, if the response body is particularly large.
- requestHeadersTextView.setText(sourceStringArray[0]);
- responseMessageTextView.setText(sourceStringArray[1]);
- responseHeadersTextView.setText(sourceStringArray[2]);
- responseBodyTextView.setText(sourceStringArray[3]);
-
- // Hide the progress bar.
- progressBar.setIndeterminate(false);
- progressBar.setVisibility(View.GONE);
-
- //Stop the swipe to refresh indicator if it is running
- swipeRefreshLayout.setRefreshing(false);
- });
-
- // Create an error observer.
- webViewSource.observeErrors().observe(this, errorString -> {
- // Display an error snackbar if the string is not `""`.
- if (!errorString.equals("")) {
- Snackbar.make(swipeRefreshLayout, errorString, Snackbar.LENGTH_LONG).show();
- }
- });
-
- // Implement swipe to refresh.
- swipeRefreshLayout.setOnRefreshListener(() -> {
- // Make the progress bar visible.
- progressBar.setVisibility(View.VISIBLE);
-
- // Set the progress bar to be indeterminate.
- progressBar.setIndeterminate(true);
-
- // Get the URL.
- String urlString = urlEditText.getText().toString();
-
- // Get the updated source.
- webViewSource.updateSource(urlString);
- });
-
- // Set the go button on the keyboard to request new source data.
- urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
- // Request new source data if the enter key was pressed.
- if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
- // Hide the soft keyboard.
- inputMethodManager.hideSoftInputFromWindow(urlEditText.getWindowToken(), 0);
-
- // Remove the focus from the URL box.
- urlEditText.clearFocus();
-
- // Make the progress bar visible.
- progressBar.setVisibility(View.VISIBLE);
-
- // Set the progress bar to be indeterminate.
- progressBar.setIndeterminate(true);
-
- // Get the URL.
- String urlString = urlEditText.getText().toString();
-
- // Get the updated source.
- webViewSource.updateSource(urlString);
-
- // Consume the key press.
- return true;
- } else {
- // Do not consume the key press.
- return false;
- }
- });
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu. This adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.view_source_options_menu, menu);
-
- // Display the menu.
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(@NonNull MenuItem menuItem) {
- // Get a handle for the about alert dialog.
- DialogFragment aboutDialogFragment = new AboutViewSourceDialog();
-
- // Show the about alert dialog.
- aboutDialogFragment.show(getSupportFragmentManager(), getString(R.string.about));
-
- // Consume the event.
- return true;
- }
-
- public void goBack(View view) {
- // Go home.
- NavUtils.navigateUpFromSameTask(activity);
- }
-
- private void highlightUrlText() {
- // Get a handle for the URL EditText.
- EditText urlEditText = findViewById(R.id.url_edittext);
-
- // Get the URL string.
- String urlString = urlEditText.getText().toString();
-
- // Highlight the URL according to the protocol.
- if (urlString.startsWith("file://")) { // This is a file URL.
- // De-emphasize only the protocol.
- urlEditText.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else if (urlString.startsWith("content://")) {
- // De-emphasize only the protocol.
- urlEditText.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else { // This is a web URL.
- // Get the index of the `/` immediately after the domain name.
- int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
-
- // Create a base URL string.
- String baseUrl;
-
- // Get the base URL.
- if (endOfDomainName > 0) { // There is at least one character after the base URL.
- // Get the base URL.
- baseUrl = urlString.substring(0, endOfDomainName);
- } else { // There are no characters after the base URL.
- // Set the base URL to be the entire URL string.
- baseUrl = urlString;
- }
-
- // Get the index of the last `.` in the domain.
- int lastDotIndex = baseUrl.lastIndexOf(".");
-
- // Get the index of the penultimate `.` in the domain.
- int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
-
- // Markup the beginning of the URL.
- if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
- urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-
- // De-emphasize subdomains.
- if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
- urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- }
- } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
- if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
- // De-emphasize the protocol and the additional subdomains.
- urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else { // There is only one subdomain in the domain name.
- // De-emphasize only the protocol.
- urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- }
- }
-
- // De-emphasize the text after the domain name.
- if (endOfDomainName > 0) {
- urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- }
- }
- }
-}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.activities
+
+import android.content.res.Configuration
+import android.os.Build
+import android.os.Bundle
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.ForegroundColorSpan
+import android.util.TypedValue
+import android.view.KeyEvent
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.View.OnFocusChangeListener
+import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import android.widget.ProgressBar
+import android.widget.TextView
+
+import androidx.appcompat.app.ActionBar
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.Toolbar
+import androidx.core.app.NavUtils
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.preference.PreferenceManager
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+
+import com.google.android.material.snackbar.Snackbar
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.dialogs.AboutViewSourceDialog
+import com.stoutner.privacybrowser.helpers.ProxyHelper
+import com.stoutner.privacybrowser.viewmodelfactories.WebViewSourceFactory
+import com.stoutner.privacybrowser.viewmodels.WebViewSource
+
+import java.util.Locale
+
+// Declare the public constants.
+const val CURRENT_URL = "current_url"
+const val USER_AGENT = "user_agent"
+
+class ViewSourceActivity: AppCompatActivity() {
+ // Declare the class variables.
+ private lateinit var initialGrayColorSpan: ForegroundColorSpan
+ private lateinit var finalGrayColorSpan: ForegroundColorSpan
+ private lateinit var redColorSpan: ForegroundColorSpan
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ // Get a handle for the shared preferences.
+ val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
+
+ // Get the screenshot preference.
+ val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+ // Disable screenshots if not allowed.
+ if (!allowScreenshots) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ }
+
+ // Set the theme.
+ setTheme(R.style.PrivacyBrowser)
+
+ // Run the default commands.
+ super.onCreate(savedInstanceState)
+
+ // Get the launching intent
+ val intent = intent
+
+ // Get the information from the intent.
+ val currentUrl = intent.getStringExtra(CURRENT_URL)
+ val userAgent = intent.getStringExtra(USER_AGENT)
+
+ // Set the content view.
+ setContentView(R.layout.view_source_coordinatorlayout)
+
+ // Get a handle for the toolbar.
+ val toolbar = findViewById<Toolbar>(R.id.view_source_toolbar)
+
+ // Set the support action bar.
+ setSupportActionBar(toolbar)
+
+ // Get a handle for the action bar.
+ val actionBar = supportActionBar!!
+
+ // Add the custom layout to the action bar.
+ actionBar.setCustomView(R.layout.view_source_app_bar)
+
+ // Instruct the action bar to display a custom layout.
+ actionBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
+
+ // Get handles for the views.
+ val urlEditText = findViewById<EditText>(R.id.url_edittext)
+ val requestHeadersTextView = findViewById<TextView>(R.id.request_headers)
+ val responseMessageTextView = findViewById<TextView>(R.id.response_message)
+ val responseHeadersTextView = findViewById<TextView>(R.id.response_headers)
+ val responseBodyTextView = findViewById<TextView>(R.id.response_body)
+ val progressBar = findViewById<ProgressBar>(R.id.progress_bar)
+ val swipeRefreshLayout = findViewById<SwipeRefreshLayout>(R.id.view_source_swiperefreshlayout)
+
+ // Populate the URL text box.
+ urlEditText.setText(currentUrl)
+
+ // Initialize the gray foreground color spans for highlighting the URLs. The deprecated `getColor()` must be used until the minimum API >= 23.
+ @Suppress("DEPRECATION")
+ initialGrayColorSpan = ForegroundColorSpan(resources.getColor(R.color.gray_500))
+ @Suppress("DEPRECATION")
+ finalGrayColorSpan = ForegroundColorSpan(resources.getColor(R.color.gray_500))
+
+ // Get the current theme status.
+ val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+ // Set the red color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
+ redColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+ @Suppress("DEPRECATION")
+ ForegroundColorSpan(resources.getColor(R.color.red_a700))
+ } else {
+ @Suppress("DEPRECATION")
+ ForegroundColorSpan(resources.getColor(R.color.red_900))
+ }
+
+ // Apply text highlighting to the URL.
+ highlightUrlText()
+
+ // Get a handle for the input method manager, which is used to hide the keyboard.
+ val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
+
+ // Remove the formatting from the URL when the user is editing the text.
+ urlEditText.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean ->
+ if (hasFocus) { // The user is editing the URL text box.
+ // Remove the highlighting.
+ urlEditText.text.removeSpan(redColorSpan)
+ urlEditText.text.removeSpan(initialGrayColorSpan)
+ urlEditText.text.removeSpan(finalGrayColorSpan)
+ } else { // The user has stopped editing the URL text box.
+ // Hide the soft keyboard.
+ inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0)
+
+ // Move to the beginning of the string.
+ urlEditText.setSelection(0)
+
+ // Reapply the highlighting.
+ highlightUrlText()
+ }
+ }
+
+ // Set the refresh color scheme according to the theme.
+ if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+ swipeRefreshLayout.setColorSchemeResources(R.color.blue_700)
+ } else {
+ swipeRefreshLayout.setColorSchemeResources(R.color.violet_500)
+ }
+
+ // Initialize a color background typed value.
+ val colorBackgroundTypedValue = TypedValue()
+
+ // Get the color background from the theme.
+ theme.resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true)
+
+ // Get the color background int from the typed value.
+ val colorBackgroundInt = colorBackgroundTypedValue.data
+
+ // Set the swipe refresh background color.
+ swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt)
+
+ // Get the Do Not Track status.
+ val doNotTrack = sharedPreferences.getBoolean(getString(R.string.do_not_track_key), false)
+
+ // Populate the locale string.
+ val localeString = if (Build.VERSION.SDK_INT >= 24) { // SDK >= 24 has a list of locales.
+ // Get the list of locales.
+ val localeList = resources.configuration.locales
+
+ // Initialize a string builder to extract the locales from the list.
+ val localesStringBuilder = StringBuilder()
+
+ // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
+ var q = 10
+
+ // Populate the string builder with the contents of the locales list.
+ for (i in 0 until localeList.size()) {
+ // Append a comma if there is already an item in the string builder.
+ if (i > 0) {
+ localesStringBuilder.append(",")
+ }
+
+ // Get the locale from the list.
+ val locale = localeList[i]
+
+ // Add the locale to the string. `locale` by default displays as `en_US`, but WebView uses the `en-US` format.
+ localesStringBuilder.append(locale.language)
+ localesStringBuilder.append("-")
+ localesStringBuilder.append(locale.country)
+
+ // If not the first locale, append `;q=0.x`, which drops by .1 for each removal from the main locale until q=0.1.
+ if (q < 10) {
+ localesStringBuilder.append(";q=0.")
+ localesStringBuilder.append(q)
+ }
+
+ // Decrement `q` if it is greater than 1.
+ if (q > 1) {
+ q--
+ }
+
+ // Add a second entry for the language only portion of the locale.
+ localesStringBuilder.append(",")
+ localesStringBuilder.append(locale.language)
+
+ // Append `1;q=0.x`, which drops by .1 for each removal form the main locale until q=0.1.
+ localesStringBuilder.append(";q=0.")
+ localesStringBuilder.append(q)
+
+ // Decrement `q` if it is greater than 1.
+ if (q > 1) {
+ q--
+ }
+ }
+
+ // Store the populated string builder in the locale string.
+ localesStringBuilder.toString()
+ } else { // SDK < 24 only has a primary locale.
+ // Store the locale in the locale string.
+ Locale.getDefault().toString()
+ }
+
+ // Instantiate the proxy helper.
+ val proxyHelper = ProxyHelper()
+
+ // Get the current proxy.
+ val proxy = proxyHelper.getCurrentProxy(this)
+
+ // Make the progress bar visible.
+ progressBar.visibility = View.VISIBLE
+
+ // Set the progress bar to be indeterminate.
+ progressBar.isIndeterminate = true
+
+ // Instantiate the WebView source factory.
+ val webViewSourceFactory: ViewModelProvider.Factory = WebViewSourceFactory(currentUrl!!, userAgent!!, doNotTrack, localeString, proxy, MainWebViewActivity.executorService)
+
+ // Instantiate the WebView source view model class.
+ val webViewSource = ViewModelProvider(this, webViewSourceFactory).get(WebViewSource::class.java)
+
+ // Create a source observer.
+ webViewSource.observeSource().observe(this, { sourceStringArray: Array<SpannableStringBuilder> ->
+ // Populate the text views. This can take a long time, and freezes the user interface, if the response body is particularly large.
+ requestHeadersTextView.text = sourceStringArray[0]
+ responseMessageTextView.text = sourceStringArray[1]
+ responseHeadersTextView.text = sourceStringArray[2]
+ responseBodyTextView.text = sourceStringArray[3]
+
+ // Hide the progress bar.
+ progressBar.isIndeterminate = false
+ progressBar.visibility = View.GONE
+
+ //Stop the swipe to refresh indicator if it is running
+ swipeRefreshLayout.isRefreshing = false
+ })
+
+ // Create an error observer.
+ webViewSource.observeErrors().observe(this, { errorString: String ->
+ // Display an error snackbar if the string is not `""`.
+ if (errorString != "") {
+ Snackbar.make(swipeRefreshLayout, errorString, Snackbar.LENGTH_LONG).show()
+ }
+ })
+
+ // Implement swipe to refresh.
+ swipeRefreshLayout.setOnRefreshListener {
+ // Make the progress bar visible.
+ progressBar.visibility = View.VISIBLE
+
+ // Set the progress bar to be indeterminate.
+ progressBar.isIndeterminate = true
+
+ // Get the URL.
+ val urlString = urlEditText.text.toString()
+
+ // Get the updated source.
+ webViewSource.updateSource(urlString)
+ }
+
+ // Set the go button on the keyboard to request new source data.
+ urlEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
+ // Request new source data if the enter key was pressed.
+ if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
+ // Hide the soft keyboard.
+ inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0)
+
+ // Remove the focus from the URL box.
+ urlEditText.clearFocus()
+
+ // Make the progress bar visible.
+ progressBar.visibility = View.VISIBLE
+
+ // Set the progress bar to be indeterminate.
+ progressBar.isIndeterminate = true
+
+ // Get the URL.
+ val urlString = urlEditText.text.toString()
+
+ // Get the updated source.
+ webViewSource.updateSource(urlString)
+
+ // Consume the key press.
+ return@setOnKeyListener true
+ } else {
+ // Do not consume the key press.
+ return@setOnKeyListener false
+ }
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ // Inflate the menu. This adds items to the action bar if it is present.
+ menuInflater.inflate(R.menu.view_source_options_menu, menu)
+
+ // Display the menu.
+ return true
+ }
+
+ override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
+ // Get a handle for the about alert dialog.
+ val aboutDialogFragment: DialogFragment = AboutViewSourceDialog()
+
+ // Show the about alert dialog.
+ aboutDialogFragment.show(supportFragmentManager, getString(R.string.about))
+
+ // Consume the event.
+ return true
+ }
+
+ // This method must be named `goBack()` and must have a View argument to match the default back arrow in the app bar.
+ fun goBack(@Suppress("UNUSED_PARAMETER") view: View) {
+ // Go home.
+ NavUtils.navigateUpFromSameTask(this)
+ }
+
+ private fun highlightUrlText() {
+ // Get a handle for the URL edit text.
+ val urlEditText = findViewById<EditText>(R.id.url_edittext)
+
+ // Get the URL string.
+ val urlString = urlEditText.text.toString()
+
+ // Highlight the URL according to the protocol.
+ if (urlString.startsWith("file://")) { // This is a file URL.
+ // De-emphasize only the protocol.
+ urlEditText.text.setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ } else if (urlString.startsWith("content://")) {
+ // De-emphasize only the protocol.
+ urlEditText.text.setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ } else { // This is a web URL.
+ // Get the index of the `/` immediately after the domain name.
+ val endOfDomainName = urlString.indexOf("/", urlString.indexOf("//") + 2)
+
+ // Create a base URL string.
+ val baseUrl: String
+
+ // Get the base URL.
+ baseUrl = if (endOfDomainName > 0) { // There is at least one character after the base URL.
+ // Get the base URL.
+ urlString.substring(0, endOfDomainName)
+ } else { // There are no characters after the base URL.
+ // Set the base URL to be the entire URL string.
+ urlString
+ }
+
+ // Get the index of the last `.` in the domain.
+ val lastDotIndex = baseUrl.lastIndexOf(".")
+
+ // Get the index of the penultimate `.` in the domain.
+ val penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1)
+
+ // Markup the beginning of the URL.
+ if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
+ urlEditText.text.setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+
+ // De-emphasize subdomains.
+ if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
+ urlEditText.text.setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ }
+ } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
+ if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
+ // De-emphasize the protocol and the additional subdomains.
+ urlEditText.text.setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ } else { // There is only one subdomain in the domain name.
+ // De-emphasize only the protocol.
+ urlEditText.text.setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ }
+ }
+
+ // De-emphasize the text after the domain name.
+ if (endOfDomainName > 0) {
+ urlEditText.text.setSpan(finalGrayColorSpan, endOfDomainName, urlString.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ }
+ }
+ }
+}
\ No newline at end of file
// Return the response body string as the result.
return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
}
-}
+}
\ No newline at end of file
package com.stoutner.privacybrowser.viewmodelfactories
-import androidx.annotation.Nullable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import java.net.Proxy
import java.util.concurrent.ExecutorService
-class WebViewSourceFactory (@Nullable private val urlString: String, private val userAgent: String, private val doNotTrack: Boolean, private val localeString: String, private val proxy: Proxy,
+class WebViewSourceFactory (private val urlString: String, private val userAgent: String, private val doNotTrack: Boolean, private val localeString: String, private val proxy: Proxy,
private val executorService: ExecutorService): ViewModelProvider.Factory {
// Override the create function in order to add the provided arguments.
override fun <T: ViewModel?> create(modelClass: Class<T>): T {
+++ /dev/null
-/*
- * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.viewmodels;
-
-import android.text.SpannableStringBuilder;
-
-import androidx.annotation.Nullable;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.ViewModel;
-
-import com.stoutner.privacybrowser.backgroundtasks.GetSourceBackgroundTask;
-
-import java.net.Proxy;
-import java.util.concurrent.ExecutorService;
-
-public class WebViewSource extends ViewModel {
- // Initialize the mutable live data variables.
- private final MutableLiveData<SpannableStringBuilder[]> mutableLiveDataSourceStringArray = new MutableLiveData<>();
- private final MutableLiveData<String> mutableLiveDataErrorString = new MutableLiveData<>();
-
- // Define the class variables.
- private final String userAgent;
- private final boolean doNotTrack;
- private final String localeString;
- private final Proxy proxy;
- private final ExecutorService executorService;
-
- // The public constructor.
- public WebViewSource(@Nullable String urlString, String userAgent, boolean doNotTrack, String localeString, Proxy proxy, ExecutorService executorService) {
- // Store the class variables.
- this.userAgent = userAgent;
- this.doNotTrack = doNotTrack;
- this.localeString = localeString;
- this.proxy = proxy;
- this.executorService = executorService;
-
- // Get the source.
- updateSource(urlString);
- }
-
- // The source observer.
- public LiveData<SpannableStringBuilder[]> observeSource() {
- // Return the source to the activity.
- return mutableLiveDataSourceStringArray;
- }
-
- // The error observer.
- public LiveData<String> observeErrors() {
- // Return any errors to the activity.
- return mutableLiveDataErrorString;
- }
-
- // The interface for returning the error from the background task
- public void returnError(String errorString) {
- // Update the mutable live data error string.
- mutableLiveDataErrorString.postValue(errorString);
- }
-
- // The workhorse that gets the source.
- public void updateSource(String urlString) {
- // Reset the mutable live data error string. This prevents the snackbar it from displaying a later if the activity restarts.
- mutableLiveDataErrorString.postValue("");
-
- // Instantiate the get source background task class.
- GetSourceBackgroundTask getSourceBackgroundTask = new GetSourceBackgroundTask();
-
- // Get the source.
- executorService.execute(() -> mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, doNotTrack, localeString, proxy, this)));
- }
-}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.viewmodels
+
+import android.text.SpannableStringBuilder
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+import com.stoutner.privacybrowser.backgroundtasks.GetSourceBackgroundTask
+
+import java.net.Proxy
+import java.util.concurrent.ExecutorService
+
+class WebViewSource(private val urlString: String, private val userAgent: String, private val doNotTrack: Boolean, private val localeString: String, private val proxy: Proxy,
+ private val executorService: ExecutorService): ViewModel() {
+ // Initialize the mutable live data variables.
+ private val mutableLiveDataSourceStringArray = MutableLiveData<Array<SpannableStringBuilder>>()
+ private val mutableLiveDataErrorString = MutableLiveData<String>()
+
+ // Initialize the view model.
+ init {
+ // Instantiate the get source background task class.
+ val getSourceBackgroundTask = GetSourceBackgroundTask()
+
+ // Get the source.
+ executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, doNotTrack, localeString, proxy, this)) }
+ }
+
+ // The source observer.
+ fun observeSource(): LiveData<Array<SpannableStringBuilder>> {
+ // Return the source to the activity.
+ return mutableLiveDataSourceStringArray
+ }
+
+ // The error observer.
+ fun observeErrors(): LiveData<String> {
+ // Return any errors to the activity.
+ return mutableLiveDataErrorString
+ }
+
+ // The interface for returning the error from the background task
+ fun returnError(errorString: String) {
+ // Update the mutable live data error string.
+ mutableLiveDataErrorString.postValue(errorString)
+ }
+
+ // The workhorse that gets the source.
+ fun updateSource(urlString: String) {
+ // Reset the mutable live data error string. This prevents the snackbar from displaying later if the activity restarts.
+ mutableLiveDataErrorString.postValue("")
+
+ // Instantiate the get source background task class.
+ val getSourceBackgroundTask = GetSourceBackgroundTask()
+
+ // Get the source.
+ executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, doNotTrack, localeString, proxy, this)) }
+ }
+}
\ No newline at end of file
<!-- Non-translatable preference keys. -->
<string name="allow_screenshots_key" translatable="false">allow_screenshots</string>
+ <string name="do_not_track_key" translatable="false">do_not_track</string>
<string name="clear_logcat_key" translatable="false">clear_logcat</string>
<string name="display_additional_app_bar_icons_key" translatable="false">display_additional_app_bar_icons</string>
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
• Adicione uma entrada Mastodon em Sobre > Links.
• Faça vários menores melhorias to a user experiência e gráfica interface.
• Tradução do português brasileiro fornecida por Thiago Nazareno Conceição Silva de Jesus.
-• Updated French translation provided by Kévin LE FLOHIC.
-• Updated German translation provided by Bernhard G. Keller.
-• Updated Italian translation provided by Francesco Buratti.
-• Updated Russian translation.
-• Updated Spanish translation provided by Jose A. León.
\ No newline at end of file
+• Tradução francesa atualizada fornecida por Kévin LE FLOHIC.
+• Tradução alemã atualizada fornecida por Bernhard G. Keller.
+• Tradução italiana atualizada fornecida por Francesco Buratti.
+• Tradução russa atualizada.
+• Tradução em espanhol atualizada fornecida por Jose A. León.
\ No newline at end of file
-# Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
+# Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
#
# This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
#
# You should have received a copy of the GNU General Public License
# along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
-
-
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
+## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
-
+#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-# org.gradle.jvmargs=-Xmx1536m
-
+# Default value: -Xmx1024m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+# Increase the amount of memory assigned to Gradle and the Kotlin Daemon.
+org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
+
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX. This is necessary for Firebase Ads in the free flavor.
-android.enableJetifier=true
\ No newline at end of file
+android.enableJetifier=true