From 33bd447a83bd3d763ee26bbb3a3f4adb074776ed Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Tue, 12 Feb 2019 21:33:26 -0700 Subject: [PATCH] Add a logcat activity. https://redmine.stoutner.com/issues/264 --- .idea/assetWizardSettings.xml | 7 +- .idea/dictionaries/soren.xml | 1 + app/src/main/AndroidManifest.xml | 18 +- .../main/assets/de/about_licenses_dark.html | 2 + .../main/assets/de/about_licenses_light.html | 2 + .../main/assets/en/about_licenses_dark.html | 2 + .../main/assets/en/about_licenses_light.html | 2 + .../main/assets/es/about_licenses_dark.html | 2 + .../main/assets/es/about_licenses_light.html | 2 + .../es/guide_ssl_certificates_dark.html | 4 +- .../es/guide_ssl_certificates_light.html | 4 +- .../main/assets/it/about_licenses_dark.html | 2 + .../main/assets/it/about_licenses_light.html | 2 + .../it/guide_ssl_certificates_dark.html | 2 +- .../it/guide_ssl_certificates_light.html | 2 +- .../main/assets/ru/about_licenses_dark.html | 2 + .../main/assets/ru/about_licenses_light.html | 2 + .../ru/guide_ssl_certificates_dark.html | 4 +- .../ru/guide_ssl_certificates_light.html | 4 +- .../assets/shared_images/file_copy_dark.png | Bin 0 -> 1220 bytes .../assets/shared_images/file_copy_light.png | Bin 0 -> 1121 bytes .../main/assets/shared_images/save_dark.png | Bin 0 -> 1432 bytes .../main/assets/shared_images/save_light.png | Bin 0 -> 1245 bytes .../main/assets/tr/about_licenses_dark.html | 2 + .../main/assets/tr/about_licenses_light.html | 2 + .../activities/ImportExportActivity.java | 81 ++-- .../activities/LogcatActivity.java | 448 ++++++++++++++++++ .../activities/MainWebViewActivity.java | 99 ++-- .../activities/RequestsActivity.java | 4 +- .../activities/ViewSourceActivity.java | 6 +- .../dialogs/EditBookmarkDialog.java | 24 +- .../dialogs/PinnedMismatchDialog.java | 5 +- .../dialogs/SaveLogcatDialog.java | 197 ++++++++ ...alog.java => StoragePermissionDialog.java} | 17 +- app/src/main/res/drawable/bug.xml | 13 + app/src/main/res/drawable/clear_dark.xml | 13 + app/src/main/res/drawable/clear_light.xml | 13 + app/src/main/res/drawable/cookies_warning.xml | 2 +- app/src/main/res/drawable/copy_dark.xml | 18 + app/src/main/res/drawable/copy_light.xml | 18 + app/src/main/res/drawable/save_dark.xml | 18 + .../main/res/drawable/save_dialog_dark.xml | 18 + .../main/res/drawable/save_dialog_light.xml | 18 + app/src/main/res/drawable/save_light.xml | 18 + app/src/main/res/drawable/select_all_dark.xml | 4 +- .../main/res/drawable/select_all_light.xml | 4 +- .../drawable/ssl_certificate_enabled_dark.xml | 4 +- .../ssl_certificate_enabled_light.xml | 4 +- .../res/layout-w900dp/bookmarks_drawer.xml | 28 +- .../layout-w900dp/domains_list_fragment.xml | 4 +- .../main/res/layout/appbar_spinner_item.xml | 1 - app/src/main/res/layout/bookmarks_drawer.xml | 24 +- .../import_export_coordinatorlayout.xml | 2 +- .../res/layout/logcat_coordinatorlayout.xml | 69 +++ app/src/main/res/layout/navigation_header.xml | 2 +- .../requests_appbar_spinner_dropdown_item.xml | 35 ++ .../layout/requests_appbar_spinner_item.xml | 31 ++ .../res/layout/requests_coordinatorlayout.xml | 6 +- .../main/res/layout/save_logcat_dialog.xml | 72 +++ app/src/main/res/menu/logcat_options_menu.xml | 48 ++ .../main/res/menu/webview_navigation_menu.xml | 16 +- app/src/main/res/values-de/strings.xml | 7 +- app/src/main/res/values-es/strings.xml | 5 +- app/src/main/res/values-it/strings.xml | 4 - app/src/main/res/values-ru/strings.xml | 5 +- app/src/main/res/values-tr/strings.xml | 5 +- app/src/main/res/values-v21/styles.xml | 6 + app/src/main/res/values/attrs.xml | 19 +- app/src/main/res/values/strings.xml | 20 +- app/src/main/res/values/styles.xml | 6 + 70 files changed, 1334 insertions(+), 197 deletions(-) create mode 100644 app/src/main/assets/shared_images/file_copy_dark.png create mode 100644 app/src/main/assets/shared_images/file_copy_light.png create mode 100644 app/src/main/assets/shared_images/save_dark.png create mode 100644 app/src/main/assets/shared_images/save_light.png create mode 100644 app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java rename app/src/main/java/com/stoutner/privacybrowser/dialogs/{ImportExportStoragePermissionDialog.java => StoragePermissionDialog.java} (82%) create mode 100644 app/src/main/res/drawable/bug.xml create mode 100644 app/src/main/res/drawable/clear_dark.xml create mode 100644 app/src/main/res/drawable/clear_light.xml create mode 100644 app/src/main/res/drawable/copy_dark.xml create mode 100644 app/src/main/res/drawable/copy_light.xml create mode 100644 app/src/main/res/drawable/save_dark.xml create mode 100644 app/src/main/res/drawable/save_dialog_dark.xml create mode 100644 app/src/main/res/drawable/save_dialog_light.xml create mode 100644 app/src/main/res/drawable/save_light.xml create mode 100644 app/src/main/res/layout/logcat_coordinatorlayout.xml create mode 100644 app/src/main/res/layout/requests_appbar_spinner_dropdown_item.xml create mode 100644 app/src/main/res/layout/requests_appbar_spinner_item.xml create mode 100644 app/src/main/res/layout/save_logcat_dialog.xml create mode 100644 app/src/main/res/menu/logcat_options_menu.xml diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index e167cf29..7958d0d2 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -68,7 +68,7 @@ @@ -78,10 +78,9 @@ diff --git a/.idea/dictionaries/soren.xml b/.idea/dictionaries/soren.xml index 87f9d7ec..e0dfdc07 100644 --- a/.idea/dictionaries/soren.xml +++ b/.idea/dictionaries/soren.xml @@ -93,6 +93,7 @@ licensors linearlayout listview + logcats logins lossless macos diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c018200e..9e443d8f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + + + edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -132,6 +133,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/de/about_licenses_light.html b/app/src/main/assets/de/about_licenses_light.html index 74116fa6..6592f4f3 100644 --- a/app/src/main/assets/de/about_licenses_light.html +++ b/app/src/main/assets/de/about_licenses_light.html @@ -112,6 +112,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -132,6 +133,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/en/about_licenses_dark.html b/app/src/main/assets/en/about_licenses_dark.html index 99cb2c6e..da0defc4 100644 --- a/app/src/main/assets/en/about_licenses_dark.html +++ b/app/src/main/assets/en/about_licenses_dark.html @@ -110,6 +110,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -130,6 +131,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/en/about_licenses_light.html b/app/src/main/assets/en/about_licenses_light.html index 8fd7c674..5ecac0a9 100644 --- a/app/src/main/assets/en/about_licenses_light.html +++ b/app/src/main/assets/en/about_licenses_light.html @@ -111,6 +111,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -131,6 +132,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/es/about_licenses_dark.html b/app/src/main/assets/es/about_licenses_dark.html index 2f92b563..1c36c979 100644 --- a/app/src/main/assets/es/about_licenses_dark.html +++ b/app/src/main/assets/es/about_licenses_dark.html @@ -115,6 +115,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -135,6 +136,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/es/about_licenses_light.html b/app/src/main/assets/es/about_licenses_light.html index 1a3aa312..1c8476f2 100644 --- a/app/src/main/assets/es/about_licenses_light.html +++ b/app/src/main/assets/es/about_licenses_light.html @@ -116,6 +116,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -136,6 +137,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/es/guide_ssl_certificates_dark.html b/app/src/main/assets/es/guide_ssl_certificates_dark.html index 1780234d..9a704795 100644 --- a/app/src/main/assets/es/guide_ssl_certificates_dark.html +++ b/app/src/main/assets/es/guide_ssl_certificates_dark.html @@ -41,8 +41,8 @@

Los certificados SSL expiran en una fecha especificada, por lo que incluso los certificados SSL fijados necesitarán legítimamente ser actualizados de vez en cuando. Como regla general, fijar los certificados SSL probablemente no sea necesario en la mayoría de los casos. - Pero para aquellos que sospechan que organizaciones poderosas puedan estar aputando hacia ellos, la fijación de certificados SSL puede detectar y frustar un ataque MITM. - Privacy Browser also has the ability to pin IP addresses.

+ Pero para aquellos que sospechan que organizaciones poderosas puedan estar apuntando hacia ellos, la fijación de certificados SSL puede detectar y frustar un ataque MITM. + Navegador Privado también tiene la capacidad de fijar direcciones IP.

diff --git a/app/src/main/assets/es/guide_ssl_certificates_light.html b/app/src/main/assets/es/guide_ssl_certificates_light.html index 20097fab..02dfecb1 100644 --- a/app/src/main/assets/es/guide_ssl_certificates_light.html +++ b/app/src/main/assets/es/guide_ssl_certificates_light.html @@ -41,8 +41,8 @@

Los certificados SSL expiran en una fecha especificada, por lo que incluso los certificados SSL fijados necesitarán legítimamente ser actualizados de vez en cuando. Como regla general, fijar los certificados SSL probablemente no sea necesario en la mayoría de los casos. - Pero para aquellos que sospechan que organizaciones poderosas puedan estar aputando hacia ellos, la fijación de certificados SSL puede detectar y frustar un ataque MITM. - Privacy Browser also has the ability to pin IP addresses.

+ Pero para aquellos que sospechan que organizaciones poderosas puedan estar apuntando hacia ellos, la fijación de certificados SSL puede detectar y frustar un ataque MITM. + Navegador Privado también tiene la capacidad de fijar direcciones IP.

diff --git a/app/src/main/assets/it/about_licenses_dark.html b/app/src/main/assets/it/about_licenses_dark.html index caba0d32..695c33dd 100644 --- a/app/src/main/assets/it/about_licenses_dark.html +++ b/app/src/main/assets/it/about_licenses_dark.html @@ -119,6 +119,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -139,6 +140,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/it/about_licenses_light.html b/app/src/main/assets/it/about_licenses_light.html index 3b92019e..f8698477 100644 --- a/app/src/main/assets/it/about_licenses_light.html +++ b/app/src/main/assets/it/about_licenses_light.html @@ -120,6 +120,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -140,6 +141,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/it/guide_ssl_certificates_dark.html b/app/src/main/assets/it/guide_ssl_certificates_dark.html index b9cdc1b9..f6713631 100644 --- a/app/src/main/assets/it/guide_ssl_certificates_dark.html +++ b/app/src/main/assets/it/guide_ssl_certificates_dark.html @@ -42,7 +42,7 @@

I certificati SSL scadono in corrispondenza di una data specifica, così anche i certificati che sono stati appuntati dovranno essere aggiornati regolarmente. Come regola generale, nella maggioranza dei casi, appuntare un certificato SSL non dovrebbe essere necessario. Per coloro che sospettano però di essere sorvegliati da qualche organizzazione, appuntare il certificato SSL può permettere di scoprire e sventare un attacco "MITM". - Privacy Browser also has the ability to pin IP addresses.

+ Privacy Browser permette anche di appuntare gli indirizzi IP.

diff --git a/app/src/main/assets/it/guide_ssl_certificates_light.html b/app/src/main/assets/it/guide_ssl_certificates_light.html index 98da47b6..88b6e16e 100644 --- a/app/src/main/assets/it/guide_ssl_certificates_light.html +++ b/app/src/main/assets/it/guide_ssl_certificates_light.html @@ -42,7 +42,7 @@

I certificati SSL scadono in corrispondenza di una data specifica, così anche i certificati che sono stati appuntati dovranno essere aggiornati regolarmente. Come regola generale, nella maggioranza dei casi, appuntare un certificato SSL non dovrebbe essere necessario. Per coloro che sospettano però di essere sorvegliati da qualche organizzazione, appuntare il certificato SSL può permettere di scoprire e sventare un attacco "MITM". - Privacy Browser also has the ability to pin IP addresses.

+ Privacy Browser permette anche di appuntare gli indirizzi IP.

diff --git a/app/src/main/assets/ru/about_licenses_dark.html b/app/src/main/assets/ru/about_licenses_dark.html index d2ed014e..fb207f62 100644 --- a/app/src/main/assets/ru/about_licenses_dark.html +++ b/app/src/main/assets/ru/about_licenses_dark.html @@ -113,6 +113,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -133,6 +134,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/ru/about_licenses_light.html b/app/src/main/assets/ru/about_licenses_light.html index 54089956..9770ee6d 100644 --- a/app/src/main/assets/ru/about_licenses_light.html +++ b/app/src/main/assets/ru/about_licenses_light.html @@ -113,6 +113,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -133,6 +134,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/ru/guide_ssl_certificates_dark.html b/app/src/main/assets/ru/guide_ssl_certificates_dark.html index dcff620d..9cb32b4b 100644 --- a/app/src/main/assets/ru/guide_ssl_certificates_dark.html +++ b/app/src/main/assets/ru/guide_ssl_certificates_dark.html @@ -40,8 +40,8 @@

Срок действия сертификатов SSL истекает в указанную дату, поэтому даже закрепленные сертификаты SSL будут периодически обновляться. Как правило, закрепление сертификатов SSL в большинстве случаев не требуется. - Но для тех, кто подозревает, что за ними ведется наблюдение, закрепление сертификата SSL может обнаружить и помешать атаке MITM. - Privacy Browser also has the ability to pin IP addresses.

+ Но для того, кто подозревает, что за ним ведется наблюдение, закрепление сертификата SSL поможет обнаружить и помешать атаке MITM. + Privacy Browser также имеет возможность закрепления IP-адресов.

diff --git a/app/src/main/assets/ru/guide_ssl_certificates_light.html b/app/src/main/assets/ru/guide_ssl_certificates_light.html index 618860e2..e566b4eb 100644 --- a/app/src/main/assets/ru/guide_ssl_certificates_light.html +++ b/app/src/main/assets/ru/guide_ssl_certificates_light.html @@ -40,8 +40,8 @@

Срок действия сертификатов SSL истекает в указанную дату, поэтому даже закрепленные сертификаты SSL будут периодически обновляться. Как правило, закрепление сертификатов SSL в большинстве случаев не требуется. - Но для тех, кто подозревает, что за ними ведется наблюдение, закрепление сертификата SSL может обнаружить и помешать атаке MITM. - Privacy Browser also has the ability to pin IP addresses.

+ Но для того, кто подозревает, что за ним ведется наблюдение, закрепление сертификата SSL поможет обнаружить и помешать атаке MITM. + Privacy Browser также имеет возможность закрепления IP-адресов.

diff --git a/app/src/main/assets/shared_images/file_copy_dark.png b/app/src/main/assets/shared_images/file_copy_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b0aa33e20a9a339c5bb63af8364df9ef708c8812 GIT binary patch literal 1220 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSEX7WqAsj$Z!;#Vf4nJ z`0WK@#^S6D5ul)CiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb2PvM=$xyGBdFFMN{`D&|Q_I-APdE@pAp8xmDCfsD)@w@o| z>w|wUk4@aURx74-(fq|q|xw5#G#ODfdy;893~AvMv>(VUBL`TOKpWM*vsviWSIMy%-Q-k zPxnsSCT)9UR}kBVU*{jcKA?Tzu+Nt-pW15PzFY3zt8iEKz}{qI0n;$u5 z;LNbN=KKSp4|$KXFS$=pY^Yw$ZlPgp&AjLCk4;yqn|K&y`s5b0&aCzL?KPpyAEz=k>au{rs#ngjjlu2joB#I8Aj4cGOqo~>778i{ zFbGTQGO=!8RCWalKQwQwmQ)3rt#ge-P@{qOv|J<849902A_sgnxh-Hwn_>u(`=pm( ze^cp%fP>{sr2q!UO+fRRQacH;@Y43mzaw>M4pvdr+@tZ+7i}z2ze=>1J<^6I=S(0H4oJ+vbquKDth-Ja<1_w!| z2@bHx({pCH#lm(f5X}rmTswOvCjjhL(|6&9>$jU&RVCDP3Kww6n&wmfMNkqg;^tt z>A-XabXxdgc8ME#?-`9)K6rm&VE(=HJA)+C551~}#?t?9fr6Pp!QB_C8$S3yyz^J! z19O4<7oFyxycT(BvDGU+h#ojo*7$w#bBPNx?@i|C3}av@zt3dHJg;wFs(^T7`q?+{ z?=dgsU^sA>pLu#?`=WHuchV15UOjbF_m9vAedFJ<6+Ayws-_*WW12VP)f4AM3=C)9 zteUx(!-ly=+SXqwydgO4+p3v=4WBk%j$!)t=-u(g%Lfb}XcRcVaoPM+_{Y@ja`*UI kH~Tj}?f`-li~cZ2*a`)&iFkVsSmH2vy85}Sb4q9e0I>@AE&u=k literal 0 HcmV?d00001 diff --git a/app/src/main/assets/shared_images/file_copy_light.png b/app/src/main/assets/shared_images/file_copy_light.png new file mode 100644 index 0000000000000000000000000000000000000000..2b8a6849a56945277f44a1994a1094a5228a6b14 GIT binary patch literal 1121 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSEX7WqAsj$Z!;#Vf4nJ z`0WK@#^S6D5ul)CiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa~t_dH!3 zLn`LHy?c7KZ=%HUkNWJ2B0HZL70#9n>`FS&(l{mP9}{z`2OFC^o6$%669TF`m+?#t zIu)Tgea5LHOC{dKuswfp_x-KcKli@=S(|nC%~Q+QHRo@=zjdwXz!@O8H1qPsx@8S_ z55y(tZrHW>@3vP$A5^M3>~vWCnB^PZ->b`Y&*saT|15M*nREg3jXM{0#1if>d}G?) zuvGS3663SJkF$CYnEp8{-%`l5gJ;dFDuM1msRN1$=T+D?N-SlRnSVk=Y?(^K(F6ZH zd6Nvim~L?C%(|i=wpgX1RCxBGSp`!V>x8D=wV2*$rog9Q$v$BYv&T6`mFEnTiWyE? zHawAWC^FsN@-@|T17`%Y4#&JyweOAn7oB^&O@T&oK9JZj_uad=c`e5eFiQ13sQ>yk zpV7U+oV9-Xp>G@!VINJ~1EZPeq?YV|`0Iru*ITA{7ZX^#80WB@TU@*A|LoWU&Iy%Y z6jVc)Zk&9PIqTY6hP=xcW;&=gaKF&=@HQ<~3*bml-=JUB(Pvr=WIrhQ!tj6P7o+u^ ztqYwP*4m^O$XkT}kow6X!gNEx)}b-;fzQM2CD&QF7}s>jEx6!%>O9M)Y^GKQku(j{ zvkvE2bmeM9#WnjA*gr5VM|~Jz&^%HHL+6 zLA9^z0|vFlGl9lS98xM!^L1GuaA3kocc7rZ6%+df{+mEU>Q#V-{GHSdG-P2M>lrsK zpnXP@Iu9@&nb-j&xcY$vckcnlj1@aCZs~GjxVerg&G>AE`K+)&jstbl&)+WSV!m)W zn}yFII3H;L1JeU%syY)i&OiD7@cI9VCK_Dn4BSs(aUsI=A=E*kl|g3$ERKRX4s;)I z;9`tWfJNMLfd=u$MiHhB4oI;H6kN~5q{))d2#ij!c+APkj_Vs&v({geTCjoXKbwy$ z|HTcf-Y>k*1dVqQrhtg{B})YwSc_ok0I0E*p+;1|LlMB^nW?A0oC6 zhwZMt1BOfaMP`#Xxo`PIm?{K;f~)%HIx(0pX3x0MdrMZMhHE=d1?QR`xh0134Z0!g z7K+R}@sp>9mCuF$(v08(i(e@2GGe`x_$7Mhp1;+*yN~hbFArll-)X<&)$L%n{MpT0 zSa$gRaSXe=j-mUQ-7a2B4jbiv4Xbnd8*4+|OT@Us8S6K)6EGBMThjL45;c{=0 z-Kb6bea`uQzOT>c`{(y+aG;Ma29E&%==%9m zPG~Y`Gf+qkcM+rdGzp$c_M@US$VNpMXnHiu_jD=%fzLMsk{Ta}(+sz!d4;BhB-7I} zqEcc&Mn;A$BZ--MK8h7%o179`sr0}DfY{8>w-T>hgr1_v4yE~QZA|*qBYQhv&Iox%!pw&@i^_bHP?@Z{AQ+S zljyj=&E)ppC;Myt2HaxriqvXCesp7*+%9WTwCJhME%*}edTv?EmCjHL@%J=?R%GZT zwS&mQ8y{#6IuY6Xp%XT(WOBpBSn;FTRrbSFbCfPeG=-KUPWp`WOHR}HH`(Q@WHWo9 zy!`DFQMltiy~oV~dj&3VRs;z)KE9Zxpls3io3(Y<&o#pJTGyUi*;T7_q~nSlo>R8} z^|ajuKS8_)r$l2NY+T7FL1u%ZG{V(UAZ$u9L8_tj*mban7E(ie&LwbYbbXtMD5P+!% zxIl>I+3p9qo^Q#~3`xU?aXj+t!mnQVe_L=jORf9b;@)A?#w|e3g{27X8?^_fyOpJ||WpFZ%hTP?QXbK!)WI)OoM^ zI%iaSmP~}{zw-2HbS%}sb6I>$`Z)b)v4hx^)-CC=C~8~dQBALA#6-*5<_Ewt1P%Y1 zL0@z{J|TMwTa?}_=^Zp;-K(#}X9I_WI@aCyGwpLV1zvt}y01x}4%Fqa?_x#jRP@>E|Ga)r=3DG7WsHY97RaSJ%M8dSR# z5EHUCC2*Ed(LR`hBApklBV2wIW)zq}ml&Ose5^{Z^av>4E&s&soHS2CiJS61PwFs= zZ9Dj&Hv4_g7IYTB*Bmghs)&luMwWl)r1Kv6<~||db=H|pqYH+JoQS43M({<1C7AGD ze|z(srLhxKy>nJ{t(#|R&IiDwQZS>9&dl%I1>9wyxKD09TeD}-t5~jb$ojwAgfAom zPGjA&Q;@qbUV!Sfr2?|vj5c0K9KgR(_Hmxh_D=isS^-c3vvV@cjp(o7@T-~i<$oEm z+C9Z($NRVb#^?w@CD%gPJ}w)zQ}Jc&Dp3bA z8!@q+eIdMb!N`8xR6I`g@6w_>%`C4dFFUdh*<(C(8sZd~ieI$Vh+~kxg%L(6tnBQ{ zIyiN_YChaQN2W(iq*shu85nY~)EpPaR0;j$f{WWZeU^tH-zGrO!KMIma@P&7vB;;S zZX==L$_+i)f$)N@_v?RW_Cp`X0ta{Rbl)8c`~&wDgOj3ZJ@{JGZ)}{eMWdAprxCDA6Gy^oj za=b=tCv=vqg<}$8k4nJ z`0WK@#^S6D5ul)CiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb2>EJY5_^ zD(1Ysd%im?ROa}{?-mQYIfYd&TvR#DdCE0~r7I*UU|W#J_kV(IU;MXpsc;@?^-yDR zb=#?_+bzbrc9CIk;F@0Uqw+S7`!fq;d^n81@BCi7teYyDEcboZ^-z(qQ zeCO*x119Fod-!YEW7ymdI6c_K_vXc>1w_l0PV&n(E&ieZLSS{*<%2v`hp#V_i(&Xxm&HzTWOzKy|ap;hV{+To5sE2Ous$+A5;V! z_^2QLZjX4v%3q8(t}Wa?z40^amxT;Hk)ID#-?;PLfKi6&o!EuxOcxg9G3KYg_{@80 z@twWDgBhl+5d4sKEbjiIJ^TBgoU3LOyWY5GEmOgTmFBDoGp#1>zUXRq{zf~~g+=R_ zUl*D0I>Ga@cUk%Uxy%8lryl5UsIQ)rFJ^J+agFQef1Eq`bI#m*y4cKi&h6d*{jzQ_ z?oR){w6Y_%?5-2LV>$UmF@ zEtsFpl-<)h`F<_mewLqUW zq34(AjjRjz!yasB`W46!b*1)!%FA>23v(IlmhF*t6loAEZ7;ch!1}-~)v8GDJ=`_F zKK!qcFaO36u_sdM;+t)+gLD|C|NQ@%aUO#iv;V{5$en8|6GEpm>dcy}tUKq+`~0AS z=*>TJAE-7PE}45SJ72J2J@2szWq#$d2Of!RXf2T}yOf}l`A+VGjBGh$x9kI^f-?y+ zlII1vtp2WJU-#($L=CUAjQ(a-sa0EKcl7>$bR};4i*FBBuZdGydbxUH)>`cY+P(3- z>aQF9HU0~|n(#`mU-qV2(Ck8mE_vy edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -130,6 +131,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/assets/tr/about_licenses_light.html b/app/src/main/assets/tr/about_licenses_light.html index 8fd7c674..5ecac0a9 100644 --- a/app/src/main/assets/tr/about_licenses_light.html +++ b/app/src/main/assets/tr/about_licenses_light.html @@ -111,6 +111,7 @@

edit.

expand_less.

expand_more.

+

file_copy.

file_download.

find_in_page.

folder.

@@ -131,6 +132,7 @@

new releases.

question_answer.

refresh.

+

save.

search.

select_all.

settings.

diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java index a2d17975..80a3a98f 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java @@ -21,9 +21,9 @@ package com.stoutner.privacybrowser.activities; import android.Manifest; import android.app.Activity; -import android.app.DialogFragment; import android.content.Intent; import android.content.pm.PackageManager; +import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -33,6 +33,7 @@ import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.design.widget.TextInputLayout; import android.support.v4.app.ActivityCompat; +import android.support.v4.app.DialogFragment; import android.support.v4.content.ContextCompat; import android.support.v4.content.FileProvider; import android.support.v7.app.ActionBar; @@ -53,7 +54,7 @@ import android.widget.Spinner; import android.widget.TextView; import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.dialogs.ImportExportStoragePermissionDialog; +import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog; import com.stoutner.privacybrowser.helpers.ImportExportDatabaseHelper; import java.io.File; @@ -70,7 +71,7 @@ import javax.crypto.CipherOutputStream; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; -public class ImportExportActivity extends AppCompatActivity implements ImportExportStoragePermissionDialog.ImportExportStoragePermissionDialogListener { +public class ImportExportActivity extends AppCompatActivity implements StoragePermissionDialog.StoragePermissionDialogListener { // Create the encryption constants. private final int NO_ENCRYPTION = 0; private final int PASSWORD_ENCRYPTION = 1; @@ -81,7 +82,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp private final int OPENPGP_EXPORT_RESULT_CODE = 1; // `openKeychainInstalled` is accessed from an inner class. - boolean openKeychainInstalled; + private boolean openKeychainInstalled; @Override public void onCreate(Bundle savedInstanceState) { @@ -161,11 +162,11 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp // Set the default file paths according to the storage permission status. if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. // Set the default file paths to use the external public directory. - defaultFilePath = Environment.getExternalStorageDirectory() + "/" + getString(R.string.privacy_browser_settings); + defaultFilePath = Environment.getExternalStorageDirectory() + "/" + getString(R.string.privacy_browser_settings_pbs); defaultPasswordEncryptionFilePath = defaultFilePath + ".aes"; } else { // The storage permission has not been granted. // Set the default file paths to use the external private directory. - defaultFilePath = getApplicationContext().getExternalFilesDir(null) + "/" + getString(R.string.privacy_browser_settings); + defaultFilePath = getApplicationContext().getExternalFilesDir(null) + "/" + getString(R.string.privacy_browser_settings_pbs); defaultPasswordEncryptionFilePath = defaultFilePath + ".aes"; } @@ -342,7 +343,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp } }); - // Hide the storage permissions TextView on API < 23 as permissions on older devices are automatically granted. + // Hide the storage permissions text view on API < 23 as permissions on older devices are automatically granted. if (Build.VERSION.SDK_INT < 23) { storagePermissionTextView.setVisibility(View.GONE); } @@ -416,15 +417,15 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp // Create the file picker intent. Intent importBrowseIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - // Set the intent MIME type to include all files. + // Set the intent MIME type to include all files so that everything is visible. importBrowseIntent.setType("*/*"); - // Set the initial directory if API >= 26. + // Set the initial directory if the minimum API >= 26. if (Build.VERSION.SDK_INT >= 26) { importBrowseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory()); } - // Specify that a file that can be opened is requested. + // Request a file that can be opened. importBrowseIntent.addCategory(Intent.CATEGORY_OPENABLE); // Launch the file picker. @@ -433,18 +434,18 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp // Create the file picker intent. Intent exportBrowseIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - // Set the intent MIME type to include all files. + // Set the intent MIME type to include all files so that everything is visible. exportBrowseIntent.setType("*/*"); // Set the initial export file name. - exportBrowseIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.privacy_browser_settings)); + exportBrowseIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.privacy_browser_settings_pbs)); - // Set the initial directory if API >= 26. + // Set the initial directory if the minimum API >= 26. if (Build.VERSION.SDK_INT >= 26) { exportBrowseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory()); } - // Specify that a file that can be opened is requested. + // Request a file that can be opened. exportBrowseIntent.addCategory(Intent.CATEGORY_OPENABLE); // Launch the file picker. @@ -479,9 +480,9 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp String fileNameString = fileNameEditText.getText().toString(); // Get the external private directory `File`. - File externalPrivateDirectoryFile = getApplicationContext().getExternalFilesDir(null); + File externalPrivateDirectoryFile = getExternalFilesDir(null); - // Remove the lint error below that the `File` might be null. + // Remove the incorrect lint error below that the file might be null. assert externalPrivateDirectoryFile != null; // Get the external private directory string. @@ -501,10 +502,10 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp // Check if the user has previously denied the storage permission. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. // Instantiate the storage permission alert dialog. - DialogFragment importExportStoragePermissionDialogFragment = new ImportExportStoragePermissionDialog(); + DialogFragment storagePermissionDialogFragment = new StoragePermissionDialog(); // Show the storage permission alert dialog. The permission will be requested when the dialog is closed. - importExportStoragePermissionDialogFragment.show(getFragmentManager(), getString(R.string.storage_permission)); + storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission)); } else { // Show the permission request directly. // Request the storage permission. The export will be run when it finishes. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); @@ -514,7 +515,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp } @Override - public void onCloseImportExportStoragePermissionDialog() { + public void onCloseStoragePermissionDialog() { // Request the write external storage permission. The import/export will be run when it finishes. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); } @@ -524,25 +525,19 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp // Get a handle for the import radiobutton. RadioButton importRadioButton = findViewById(R.id.import_radiobutton); - // Check to see if import or export is selected. - if (importRadioButton.isChecked()) { // Import is selected. - // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. - if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + // Run the import or export methods according to which radio button is selected. + if (importRadioButton.isChecked()) { // Import is selected. // Import the settings. importSettings(); - } else { // The storage permission was not granted. - // Display an error snackbar. - Snackbar.make(importRadioButton, getString(R.string.cannot_import), Snackbar.LENGTH_LONG).show(); - } - } else { // Export is selected. - // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. - if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + } else { // Export is selected. // Export the settings. exportSettings(); - } else { // The storage permission was not granted. - // Display an error snackbar. - Snackbar.make(importRadioButton, getString(R.string.cannot_export), Snackbar.LENGTH_LONG).show(); } + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(importRadioButton, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); } } @@ -552,7 +547,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp case (BROWSE_RESULT_CODE): // Don't do anything if the user pressed back from the file picker. if (resultCode == Activity.RESULT_OK) { - // Get a handle for the file name EditText. + // Get a handle for the file name edit text. EditText fileNameEditText = findViewById(R.id.file_name_edittext); // Get the file name URI. @@ -564,7 +559,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp // Get the raw file name path. String rawFileNamePath = fileNameUri.getPath(); - // Remove the warning that the file name path might be null. + // Remove the incorrect lint warning that the file name path might be null. assert rawFileNamePath != null; // Check to see if the file name Path includes a valid storage location. @@ -604,7 +599,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp case OPENPGP_EXPORT_RESULT_CODE: // Get the temporary unencrypted export file. - File temporaryUnencryptedExportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.privacy_browser_settings)); + File temporaryUnencryptedExportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.privacy_browser_settings_pbs)); // Delete the temporary unencrypted export file if it exists. if (temporaryUnencryptedExportFile.exists()) { @@ -623,11 +618,14 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp // Instantiate the import export database helper. ImportExportDatabaseHelper importExportDatabaseHelper = new ImportExportDatabaseHelper(); + // Get the export file string. + String exportFileString = fileNameEditText.getText().toString(); + // Get the export and temporary unencrypted export files. - File exportFile = new File(fileNameEditText.getText().toString()); - File temporaryUnencryptedExportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.privacy_browser_settings)); + File exportFile = new File(exportFileString); + File temporaryUnencryptedExportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.privacy_browser_settings_pbs)); - // Initialize the export status string. + // Create an export status string. String exportStatus; // Export according to the encryption type. @@ -776,6 +774,9 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp startActivityForResult(openKeychainEncryptIntent, OPENPGP_EXPORT_RESULT_CODE); break; } + + // Add the file to the list of recent files. This doesn't currently work, but maybe it will someday. + MediaScannerConnection.scanFile(this, new String[] {exportFileString}, new String[] {"application/x-sqlite3"}, null); } private void importSettings() { @@ -801,7 +802,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp case PASSWORD_ENCRYPTION: // Use a private temporary import location. - File temporaryUnencryptedImportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.privacy_browser_settings)); + File temporaryUnencryptedImportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.privacy_browser_settings_pbs)); try { // Create an encrypted import file input stream. diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java new file mode 100644 index 00000000..32d1bf47 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java @@ -0,0 +1,448 @@ +/* + * Copyright © 2019 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.Manifest; +import android.app.Activity; +import android.app.Dialog; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.DialogFragment; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AppCompatDialogFragment; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog; +import com.stoutner.privacybrowser.dialogs.SaveLogcatDialog; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; + +public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialog.SaveLogcatListener, StoragePermissionDialog.StoragePermissionDialogListener { + private String filePathString; + + @Override + public void onCreate(Bundle savedInstanceState) { + // Disable screenshots if not allowed. + if (!MainWebViewActivity.allowScreenshots) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + + // Set the activity theme. + if (MainWebViewActivity.darkTheme) { + setTheme(R.style.PrivacyBrowserDark_SecondaryActivity); + } else { + setTheme(R.style.PrivacyBrowserLight_SecondaryActivity); + } + + // Run the default commands. + super.onCreate(savedInstanceState); + + // Set the content view. + setContentView(R.layout.logcat_coordinatorlayout); + + // Use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. + Toolbar logcatAppBar = findViewById(R.id.logcat_toolbar); + setSupportActionBar(logcatAppBar); + + // Get a handle for the app bar. + ActionBar appBar = getSupportActionBar(); + + // Remove the incorrect lint warning that `appBar` might be null. + assert appBar != null; + + // Display the the back arrow in the app bar. + appBar.setDisplayHomeAsUpEnabled(true); + + // Implement swipe to refresh. + SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.logcat_swiperefreshlayout); + swipeRefreshLayout.setOnRefreshListener(() -> { + // Get the current logcat. + new GetLogcat(this).execute(); + }); + + // Set the swipe to refresh color according to the theme. + if (MainWebViewActivity.darkTheme) { + swipeRefreshLayout.setColorSchemeResources(R.color.blue_600); + swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_800); + } else { + swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); + } + + // Get the logcat. + new GetLogcat(this).execute(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu. This adds items to the action bar. + getMenuInflater().inflate(R.menu.logcat_options_menu, menu); + + // Display the menu. + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + // Get the selected menu item ID. + int menuItemId = menuItem.getItemId(); + + // Run the commands that correlate to the selected menu item. + switch (menuItemId) { + case R.id.copy: + // Get a handle for the clipboard manager. + ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + + // Get a handle for the logcat text view. + TextView logcatTextView = findViewById(R.id.logcat_textview); + + // Save the logcat in a ClipData. + ClipData logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.getText()); + + // Remove the incorrect lint error that `clipboardManager.setPrimaryClip()` might produce a null pointer exception. + assert clipboardManager != null; + + // Place the ClipData on the clipboard. + clipboardManager.setPrimaryClip(logcatClipData); + + // Display a snackbar. + Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show(); + + // Consume the event. + return true; + + case R.id.save: + // Get a handle for the save alert dialog. + DialogFragment saveDialogFragment = new SaveLogcatDialog(); + + // Show the save alert dialog. + saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_logcat)); + + // Consume the event. + return true; + + case R.id.clear: + try { + // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system). + Process process = Runtime.getRuntime().exec("logcat -b all -c"); + + // Wait for the process to finish. + process.waitFor(); + + // Reload the logcat. + new GetLogcat(this).execute(); + } catch (IOException|InterruptedException exception) { + // Do nothing. + } + + // Consume the event. + return true; + + default: + // Don't consume the event. + return super.onOptionsItemSelected(menuItem); + } + } + + @Override + public void onSaveLogcat(AppCompatDialogFragment dialogFragment) { + // Get a handle for the file name edit text. + EditText fileNameEditText = dialogFragment.getDialog().findViewById(R.id.file_name_edittext); + + // Get the file path string. + filePathString = fileNameEditText.getText().toString(); + + // Check to see if the storage permission is needed. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. + // Save the logcat. + saveLogcat(filePathString); + } else { // The storage permission has not been granted. + // Get the external private directory `File`. + File externalPrivateDirectoryFile = getExternalFilesDir(null); + + // Remove the incorrect lint error below that the file might be null. + assert externalPrivateDirectoryFile != null; + + // Get the external private directory string. + String externalPrivateDirectory = externalPrivateDirectoryFile.toString(); + + // Check to see if the file path is in the external private directory. + if (filePathString.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory. + // Save the logcat. + saveLogcat(filePathString); + } else { // The file path in in a public directory. + // Check if the user has previously denied the storage permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Instantiate the storage permission alert dialog. + DialogFragment storagePermissionDialogFragment = new StoragePermissionDialog(); + + // Show the storage permission alert dialog. The permission will be requested when the dialog is closed. + storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission)); + } else { // Show the permission request directly. + // Request the storage permission. The logcat will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); + + } + } + } + } + + @Override + public void onCloseStoragePermissionDialog() { + // Request the write external storage permission. The logcat will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + // Check to see if the storage permission was granted. If the dialog was canceled the grant result will be empty. + if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + // Save the logcat. + saveLogcat(filePathString); + } else { // The storage permission was not granted. + // Get a handle for the logcat text view. + TextView logcatTextView = findViewById(R.id.logcat_textview); + + // Display an error snackbar. + Snackbar.make(logcatTextView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); + } + } + + private void saveLogcat(String fileNameString) { + // Get a handle for the logcat text view. + TextView logcatTextView = findViewById(R.id.logcat_textview); + + try { + // Get the logcat as a string. + String logcatString = logcatTextView.getText().toString(); + + // Create an input stream with the contents of the logcat. + InputStream logcatInputStream = new ByteArrayInputStream(logcatString.getBytes(StandardCharsets.UTF_8)); + + // Create a logcat buffered reader. + BufferedReader logcatBufferedReader = new BufferedReader(new InputStreamReader(logcatInputStream)); + + // Create a file from the file name string. + File saveFile = new File(fileNameString); + + // Create a file buffered writer. + BufferedWriter fileBufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(saveFile))); + + // Create a transfer string. + String transferString; + + // Use the transfer string to copy the logcat from the buffered reader to the buffered writer. + while ((transferString = logcatBufferedReader.readLine()) != null) { + // Append the line to the buffered writer. + fileBufferedWriter.append(transferString); + + // Append a line break. + fileBufferedWriter.append("\n"); + } + + // Close the buffered reader and writer. + logcatBufferedReader.close(); + fileBufferedWriter.close(); + + // Add the file to the list of recent files. This doesn't currently work, but maybe it will someday. + MediaScannerConnection.scanFile(this, new String[] {fileNameString}, new String[] {"text/plain"}, null); + + // Display a snackbar. + Snackbar.make(logcatTextView, getString(R.string.file_saved_successfully), Snackbar.LENGTH_SHORT).show(); + } catch (Exception exception) { + // Display a snackbar with the error message. + Snackbar.make(logcatTextView, getString(R.string.save_failed) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show(); + } + } + + // The activity result is called after browsing for a file in the save alert dialog. + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // Don't do anything if the user pressed back from the file picker. + if (resultCode == Activity.RESULT_OK) { + // Get a handle for the save dialog fragment. + DialogFragment saveDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_logcat)); + + // Remove the incorrect lint error that the save dialog fragment might be null. + assert saveDialogFragment != null; + + // Get a handle for the save dialog. + Dialog saveDialog = saveDialogFragment.getDialog(); + + // Get a handle for the file name edit text. + EditText fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext); + + // Get the file name URI. + Uri fileNameUri = data.getData(); + + // Remove the incorrect lint warning that the file name URI might be null. + assert fileNameUri != null; + + // Get the raw file name path. + String rawFileNamePath = fileNameUri.getPath(); + + // Remove the incorrect lint warning that the file name path might be null. + assert rawFileNamePath != null; + + // Check to see if the file name Path includes a valid storage location. + if (rawFileNamePath.contains(":")) { // The path is valid. + // Split the path into the initial content uri and the final path information. + String fileNameContentPath = rawFileNamePath.substring(0, rawFileNamePath.indexOf(":")); + String fileNameFinalPath = rawFileNamePath.substring(rawFileNamePath.indexOf(":") + 1); + + // Create the file name path string. + String fileNamePath; + + // Construct the file name path. + switch (fileNameContentPath) { + // The documents home has a special content path. + case "/document/home": + fileNamePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + fileNameFinalPath; + break; + + // Everything else for the primary user should be in `/document/primary`. + case "/document/primary": + fileNamePath = Environment.getExternalStorageDirectory() + "/" + fileNameFinalPath; + break; + + // Just in case, catch everything else and place it in the external storage directory. + default: + fileNamePath = Environment.getExternalStorageDirectory() + "/" + fileNameFinalPath; + break; + } + + // Set the file name path as the text of the file name edit text. + fileNameEditText.setText(fileNamePath); + } else { // The path is invalid. + // Close the alert dialog. + saveDialog.dismiss(); + + // Get a handle for the logcat text view. + TextView logcatTextView = findViewById(R.id.logcat_textview); + + // Display a snackbar with the error message. + Snackbar.make(logcatTextView, rawFileNamePath + " " + getString(R.string.invalid_location), Snackbar.LENGTH_INDEFINITE).show(); + } + } + } + + // `Void` does not declare any parameters. `Void` does not declare progress units. `String` contains the results. + private static class GetLogcat extends AsyncTask { + // Create a weak reference to the calling activity. + private final WeakReference activityWeakReference; + + // Populate the weak reference to the calling activity. + GetLogcat(Activity activity) { + activityWeakReference = new WeakReference<>(activity); + } + + @Override + protected String doInBackground(Void... parameters) { + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + return ""; + } + + // Create a log string builder. + StringBuilder logStringBuilder = new StringBuilder(); + + try { + // Get the logcat. `-b all` gets all the buffers (instead of just crash, main, and system). `-v long` produces more complete information. `-d` dumps the logcat and exits. + Process process = Runtime.getRuntime().exec("logcat -b all -v long -d"); + + // Wrap the logcat in a buffered reader. + BufferedReader logBufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); + + // Create a log transfer string. + String logTransferString; + + // Use the log transfer string to copy the logcat from the buffered reader to the string builder. + while ((logTransferString = logBufferedReader.readLine()) != null) { + // Append a line. + logStringBuilder.append(logTransferString); + + // Append a line break. + logStringBuilder.append("\n"); + } + + // Close the buffered reader. + logBufferedReader.close(); + } catch (IOException exception) { + // Do nothing. + } + + // Return the logcat. + return logStringBuilder.toString(); + } + + // `onPostExecute()` operates on the UI thread. + @Override + protected void onPostExecute(String logcatString) { + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + return; + } + + // Get handles for the views. + TextView logcatTextView = activity.findViewById(R.id.logcat_textview); + SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.logcat_swiperefreshlayout); + + // Display the logcat. + logcatTextView.setText(logcatString); + + // Stop the swipe to refresh animation if it is displayed. + swipeRefreshLayout.setRefreshing(false); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 65528ebf..868b6d71 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -2046,7 +2046,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. + // Inflate the menu. This adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.webview_options_menu, menu); // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`. @@ -2312,7 +2312,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the selected menu item ID. int menuItemId = menuItem.getItemId(); - // Set the commands that relate to the menu entries. + // Run the commands that correlate to the selected menu item. switch (menuItemId) { case R.id.toggle_javascript: // Switch the status of javaScriptEnabled. @@ -2532,15 +2532,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Setup a runnable to manually delete the DOM storage files and directories. Runnable deleteDomStorageRunnable = () -> { try { - // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); + // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. + Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); // Multiple commands must be used because `Runtime.exec()` does not like `*`. - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); - privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); - privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); - } catch (IOException e) { + Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); + Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); + Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); + Process deleteDatabasesProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); + + // Wait for the processes to finish. + deleteLocalStorageProcess.waitFor(); + deleteIndexProcess.waitFor(); + deleteQuotaManagerProcess.waitFor(); + deleteQuotaManagerJournalProcess.waitFor(); + deleteDatabasesProcess.waitFor(); + } catch (Exception exception) { // Do nothing if an error is thrown. } }; @@ -3016,6 +3023,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook startActivity(importExportIntent); break; + case R.id.logcat: + // Launch the logcat activity. + Intent logcatIntent = new Intent(this, LogcatActivity.class); + startActivity(logcatIntent); + break; + case R.id.guide: // Launch `GuideActivity`. Intent guideIntent = new Intent(this, GuideActivity.class); @@ -3028,14 +3041,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook startActivity(aboutIntent); break; - case R.id.clearAndExit: + case R.id.clear_and_exit: // Close the bookmarks cursor and database. bookmarksCursor.close(); bookmarksDatabaseHelper.close(); - // Get a handle for `sharedPreferences`. `this` references the current context. + // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + // Get the status of the clear everything preference. boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true); // Clear cookies. @@ -3049,10 +3063,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run. try { - // We have to use two commands because `Runtime.exec()` does not like `*`. - privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies"); - privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal"); - } catch (IOException e) { + // Two commands must be used because `Runtime.exec()` does not like `*`. + Process deleteCookiesProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies"); + Process deleteCookiesJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal"); + + // Wait until the processes have finished. + deleteCookiesProcess.waitFor(); + deleteCookiesJournalProcess.waitFor(); + } catch (Exception exception) { // Do nothing if an error is thrown. } } @@ -3066,14 +3084,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run. try { // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); + Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); // Multiple commands must be used because `Runtime.exec()` does not like `*`. - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); - privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); - privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); - } catch (IOException e) { + Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); + Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); + Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); + Process deleteDatabaseProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); + + // Wait until the processes have finished. + deleteLocalStorageProcess.waitFor(); + deleteIndexProcess.waitFor(); + deleteQuotaManagerProcess.waitFor(); + deleteQuotaManagerJournalProcess.waitFor(); + deleteDatabaseProcess.waitFor(); + } catch (Exception exception) { // Do nothing if an error is thrown. } } @@ -3085,10 +3110,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run. try { - // We have to use a `String[]` because the database contains a space and `Runtime.exec` will not escape the string correctly otherwise. - privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"}); - privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"}); - } catch (IOException e) { + // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly. + Process deleteWebDataProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"}); + Process deleteWebDataJournalProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"}); + + // Wait until the processes have finished. + deleteWebDataProcess.waitFor(); + deleteWebDataJournalProcess.waitFor(); + } catch (Exception exception) { // Do nothing if an error is thrown. } } @@ -3101,12 +3130,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Manually delete the cache directories. try { // Delete the main cache directory. - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); + Process deleteCacheProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); // Delete the secondary `Service Worker` cache directory. - // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. - privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); - } catch (IOException e) { + // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. + Process deleteServiceWorkerProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); + + // Wait until the processes have finished. + deleteCacheProcess.waitFor(); + deleteServiceWorkerProcess.waitFor(); + } catch (Exception exception) { // Do nothing if an error is thrown. } } @@ -3133,8 +3166,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. if (clearEverything) { try { - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); - } catch (IOException e) { + // Delete the folder. + Process deleteAppWebviewProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); + + // Wait until the process has finished. + deleteAppWebviewProcess.waitFor(); + } catch (Exception exception) { // Do nothing if an error is thrown. } } diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java index 71ce3070..098fd66f 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java @@ -137,7 +137,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi spinnerCursor.addRow(new Object[]{4, getString(R.string.blocked_plural) + " - " + blockedResourceRequests.size()}); // Create a resource cursor adapter for the spinner. - ResourceCursorAdapter spinnerCursorAdapter = new ResourceCursorAdapter(this, R.layout.appbar_spinner_item, spinnerCursor, 0) { + ResourceCursorAdapter spinnerCursorAdapter = new ResourceCursorAdapter(this, R.layout.requests_appbar_spinner_item, spinnerCursor, 0) { @Override public void bindView(View view, Context context, Cursor cursor) { // Get a handle for the spinner item text view. @@ -149,7 +149,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi }; // Set the resource cursor adapter drop down view resource. - spinnerCursorAdapter.setDropDownViewResource(R.layout.appbar_spinner_dropdown_item); + spinnerCursorAdapter.setDropDownViewResource(R.layout.requests_appbar_spinner_dropdown_item); // Get a handle for the app bar spinner and set the adapter. Spinner appBarSpinner = findViewById(R.id.spinner); diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java index 8e7f5e96..62c704de 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java @@ -206,7 +206,7 @@ public class ViewSourceActivity extends AppCompatActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. + // 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. @@ -291,7 +291,7 @@ public class ViewSourceActivity extends AppCompatActivity { } } - // `String` declares the parameters. `Void` does not declare progress units. `String[]` contains the results. + // `String` declares the parameters. `Void` does not declare progress units. `SpannableStringBuilder[]` contains the results. private static class GetSource extends AsyncTask { // Create a weak reference to the calling activity. private WeakReference activityWeakReference; @@ -663,7 +663,7 @@ public class ViewSourceActivity extends AppCompatActivity { // `onPostExecute()` operates on the UI thread. @Override protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){ - // Get a handle the activity. + // Get a handle for the activity. Activity activity = activityWeakReference.get(); // Abort if the activity is gone. diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.java index 0bb5f9da..64efa611 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.java @@ -116,27 +116,27 @@ public class EditBookmarkDialog extends AppCompatDialogFragment { // Set the title. dialogBuilder.setTitle(R.string.edit_bookmark); - // Remove the incorrect lint warning that `getActivity()` might be null. + // Remove the incorrect lint warning that `getActivity().getLayoutInflater()` might be null. assert getActivity() != null; // Set the view. The parent view is null because it will be assigned by the alert dialog. dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.edit_bookmark_dialog, null)); - // Set the listener for the negative button. + // Set the cancel button listener. dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { - // Do nothing. The `AlertDialog` will close automatically. + // Do nothing. The alert dialog will close automatically. }); - // Set the listener fo the positive button. + // Set the save button listener. dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> { - // Return the `DialogFragment` to the parent activity on save. + // Return the dialog fragment to the parent activity. editBookmarkListener.onSaveBookmark(EditBookmarkDialog.this, selectedBookmarkDatabaseId); }); - // Create an alert dialog from the alert dialog builder. + // Create an alert dialog from the builder. final AlertDialog alertDialog = dialogBuilder.create(); - // Remove the warning below that `getWindow()` might be null. + // remove the incorrect lint warning below that `getWindow().addFlags()` might be null. assert alertDialog.getWindow() != null; // Disable screenshots if not allowed. @@ -175,7 +175,7 @@ public class EditBookmarkDialog extends AppCompatDialogFragment { currentName = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); currentUrl = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)); - // Populate the `EditTexts`. + // Populate the edit texts. nameEditText.setText(currentName); urlEditText.setText(currentUrl); @@ -226,7 +226,7 @@ public class EditBookmarkDialog extends AppCompatDialogFragment { } }); - // Allow the `enter` key on the keyboard to save the bookmark from the bookmark name `EditText`. + // Allow the enter key on the keyboard to save the bookmark from the bookmark name edit text. nameEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> { // Save the bookmark if the event is a key-down on the "enter" button. if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER) && editButton.isEnabled()) { // The enter key was pressed and the edit button is enabled. @@ -243,14 +243,14 @@ public class EditBookmarkDialog extends AppCompatDialogFragment { } }); - // Allow the "enter" key on the keyboard to save the bookmark from the URL `EditText`. + // Allow the enter key on the keyboard to save the bookmark from the URL edit text. urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> { // Save the bookmark if the event is a key-down on the "enter" button. if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER) && editButton.isEnabled()) { // The enter key was pressed and the edit button is enabled. // Trigger the `Listener` and return the DialogFragment to the parent activity. editBookmarkListener.onSaveBookmark(EditBookmarkDialog.this, selectedBookmarkDatabaseId); - // Manually dismiss the `AlertDialog`. + // Manually dismiss the alert dialog. alertDialog.dismiss(); // Consume the event. @@ -260,7 +260,7 @@ public class EditBookmarkDialog extends AppCompatDialogFragment { } }); - // `onCreateDialog` requires the return of an `AlertDialog`. + // Return the alert dialog. return alertDialog; } diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java index 2d506dc4..0a25771d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java @@ -126,14 +126,15 @@ public class PinnedMismatchDialog extends AppCompatDialogFragment { pinnedSslCertificate = getArguments().getBoolean("Pinned_SSL_Certificate"); pinnedIpAddresses = getArguments().getBoolean("Pinned_IP_Addresses"); - if (MainWebViewActivity.favoriteIconBitmap.equals(MainWebViewActivity.favoriteIconDefaultBitmap)) { + // Set the favorite icon as the dialog icon if it exists. + if (MainWebViewActivity.favoriteIconBitmap.equals(MainWebViewActivity.favoriteIconDefaultBitmap)) { // There is no favorite icon. // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark); } else { dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light); } - } else { + } else { // There is a favorite icon. // Create a drawable version of the favorite icon. Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIconBitmap); diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java new file mode 100644 index 00000000..4faafcba --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java @@ -0,0 +1,197 @@ +/* + * Copyright © 2016-2019 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +// `AppCompatDialogFragment` is required instead of `DialogFragment` or an error is produced on API <=22. It is also required for the browser button to work correctly. +import android.support.v7.app.AppCompatDialogFragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.activities.MainWebViewActivity; + +public class SaveLogcatDialog extends AppCompatDialogFragment { + // Instantiate the class variables. + private SaveLogcatListener saveLogcatListener; + private Context parentContext; + + // The public interface is used to send information back to the parent activity. + public interface SaveLogcatListener { + void onSaveLogcat(AppCompatDialogFragment dialogFragment); + } + + public void onAttach(Context context) { + // Run the default commands. + super.onAttach(context); + + // Store a handle for the context. + parentContext = context; + + // Get a handle for `SaveLogcatListener` from the launching context. + saveLogcatListener = (SaveLogcatListener) context; + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use an alert dialog builder to create the alert dialog. + AlertDialog.Builder dialogBuilder; + + // Set the style according to the theme. + if (MainWebViewActivity.darkTheme) { + dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark); + } else { + dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight); + } + + // Set the title. + dialogBuilder.setTitle(R.string.save_logcat); + + // Remove the incorrect lint warning that `getActivity().getLayoutInflater()` might be null. + assert getActivity() != null; + + // Set the view. The parent view is null because it will be assigned by the alert dialog. + dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.save_logcat_dialog, null)); + + // Set the icon according to the theme. + if (MainWebViewActivity.darkTheme) { + dialogBuilder.setIcon(R.drawable.save_dialog_dark); + } else { + dialogBuilder.setIcon(R.drawable.save_dialog_light); + } + + // Set the cancel button listener. + dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { + // Do nothing. The alert dialog will close automatically. + }); + + // Set the save button listener. + dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> { + // Return the dialog fragment to the parent activity. + saveLogcatListener.onSaveLogcat(this); + }); + + // Create an alert dialog from the builder. + AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the incorrect lint warning below that `getWindow().addFlags()` might be null. + assert alertDialog.getWindow() != null; + + // Disable screenshots if not allowed. + if (!MainWebViewActivity.allowScreenshots) { + alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + + // The alert dialog must be shown before items in the layout can be modified. + alertDialog.show(); + + // Get handles for the layout items. + EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext); + Button browseButton = alertDialog.findViewById(R.id.browse_button); + TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview); + Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + + // Create a string for the default file path. + String defaultFilePath; + + // Set the default file path according to the storage permission state. + if (ContextCompat.checkSelfPermission(parentContext, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. + // Set the default file path to use the external public directory. + defaultFilePath = Environment.getExternalStorageDirectory() + "/" + getString(R.string.privacy_browser_logcat_txt); + } else { // The storage permission has not been granted. + // Set the default file path to use the external private directory. + defaultFilePath = parentContext.getExternalFilesDir(null) + "/" + getString(R.string.privacy_browser_logcat_txt); + } + + // Display the default file path. + fileNameEditText.setText(defaultFilePath); + + // Update the status of the save button when the file name changes. + fileNameEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Do nothing. + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Do nothing. + } + + @Override + public void afterTextChanged(Editable s) { + // Enable the save button if a file name exists. + saveButton.setEnabled(!fileNameEditText.getText().toString().isEmpty()); + } + }); + + // Handle clicks on the browse button. + browseButton.setOnClickListener((View view) -> { + // Create the file picker intent. + Intent browseIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + + // Set the intent MIME type to include all files so that everything is visible. + browseIntent.setType("*/*"); + + // Set the initial file name. + browseIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.privacy_browser_logcat_txt)); + + // Set the initial directory if the minimum API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + browseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory()); + } + + // Request a file that can be opened. + browseIntent.addCategory(Intent.CATEGORY_OPENABLE); + + // Launch the file picker. There is only one `startActivityForResult()`, so the request code is simply set to 0. + startActivityForResult(browseIntent, 0); + }); + + // Hide the storage permission text view on API < 23 as permissions on older devices are automatically granted. + if (Build.VERSION.SDK_INT < 23) { + storagePermissionTextView.setVisibility(View.GONE); + } + + // Return the alert dialog. + return alertDialog; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java similarity index 82% rename from app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java rename to app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java index 447b67e6..ed119595 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java @@ -21,22 +21,24 @@ package com.stoutner.privacybrowser.dialogs; import android.app.AlertDialog; import android.app.Dialog; -import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; +// `AppCompatDialogFragment` must be used instead of `DialogFragment` or the browse button doesn't work correctly in the other dialog for saving logcats. +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatDialogFragment; import android.view.WindowManager; import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.activities.MainWebViewActivity; -public class ImportExportStoragePermissionDialog extends DialogFragment { +public class StoragePermissionDialog extends AppCompatDialogFragment { // The listener is used in `onAttach()` and `onCreateDialog()`. - private ImportExportStoragePermissionDialogListener importExportStoragePermissionDialogListener; + private StoragePermissionDialogListener storagePermissionDialogListener; // The public interface is used to send information back to the parent activity. - public interface ImportExportStoragePermissionDialogListener { - void onCloseImportExportStoragePermissionDialog(); + public interface StoragePermissionDialogListener { + void onCloseStoragePermissionDialog(); } @Override @@ -45,9 +47,10 @@ public class ImportExportStoragePermissionDialog extends DialogFragment { super.onAttach(context); // Get a handle for the listener from the launching context. - importExportStoragePermissionDialogListener = (ImportExportStoragePermissionDialogListener) context; + storagePermissionDialogListener = (StoragePermissionDialogListener) context; } + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Use a builder to create the alert dialog. @@ -71,7 +74,7 @@ public class ImportExportStoragePermissionDialog extends DialogFragment { // Set an `onClick` listener on the negative button. dialogBuilder.setNegativeButton(R.string.ok, (DialogInterface dialog, int which) -> { // Inform the parent activity that the dialog was closed. - importExportStoragePermissionDialogListener.onCloseImportExportStoragePermissionDialog(); + storagePermissionDialogListener.onCloseStoragePermissionDialog(); }); // Create an alert dialog from the builder. diff --git a/app/src/main/res/drawable/bug.xml b/app/src/main/res/drawable/bug.xml new file mode 100644 index 00000000..c21c2eb6 --- /dev/null +++ b/app/src/main/res/drawable/bug.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/clear_dark.xml b/app/src/main/res/drawable/clear_dark.xml new file mode 100644 index 00000000..738b53fd --- /dev/null +++ b/app/src/main/res/drawable/clear_dark.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/clear_light.xml b/app/src/main/res/drawable/clear_light.xml new file mode 100644 index 00000000..a30170d5 --- /dev/null +++ b/app/src/main/res/drawable/clear_light.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/cookies_warning.xml b/app/src/main/res/drawable/cookies_warning.xml index 733448f6..6f497590 100644 --- a/app/src/main/res/drawable/cookies_warning.xml +++ b/app/src/main/res/drawable/cookies_warning.xml @@ -11,7 +11,7 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + diff --git a/app/src/main/res/drawable/copy_dark.xml b/app/src/main/res/drawable/copy_dark.xml new file mode 100644 index 00000000..f37115ab --- /dev/null +++ b/app/src/main/res/drawable/copy_dark.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/copy_light.xml b/app/src/main/res/drawable/copy_light.xml new file mode 100644 index 00000000..1c2dc993 --- /dev/null +++ b/app/src/main/res/drawable/copy_light.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/save_dark.xml b/app/src/main/res/drawable/save_dark.xml new file mode 100644 index 00000000..e26d3bfd --- /dev/null +++ b/app/src/main/res/drawable/save_dark.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/save_dialog_dark.xml b/app/src/main/res/drawable/save_dialog_dark.xml new file mode 100644 index 00000000..f49b8abf --- /dev/null +++ b/app/src/main/res/drawable/save_dialog_dark.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/save_dialog_light.xml b/app/src/main/res/drawable/save_dialog_light.xml new file mode 100644 index 00000000..e4592c84 --- /dev/null +++ b/app/src/main/res/drawable/save_dialog_light.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/save_light.xml b/app/src/main/res/drawable/save_light.xml new file mode 100644 index 00000000..df119f5c --- /dev/null +++ b/app/src/main/res/drawable/save_light.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/select_all_dark.xml b/app/src/main/res/drawable/select_all_dark.xml index 6f3fad01..61008a29 100644 --- a/app/src/main/res/drawable/select_all_dark.xml +++ b/app/src/main/res/drawable/select_all_dark.xml @@ -6,8 +6,8 @@ android:viewportHeight="24.0" android:viewportWidth="24.0" > - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/select_all_light.xml b/app/src/main/res/drawable/select_all_light.xml index 590eca08..e5ed2b13 100644 --- a/app/src/main/res/drawable/select_all_light.xml +++ b/app/src/main/res/drawable/select_all_light.xml @@ -6,8 +6,8 @@ android:viewportHeight="24.0" android:viewportWidth="24.0" > - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml b/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml index d0ff3a30..9d5436c1 100644 --- a/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml +++ b/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml @@ -11,8 +11,8 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ssl_certificate_enabled_light.xml b/app/src/main/res/drawable/ssl_certificate_enabled_light.xml index 42407ce7..80d5541d 100644 --- a/app/src/main/res/drawable/ssl_certificate_enabled_light.xml +++ b/app/src/main/res/drawable/ssl_certificate_enabled_light.xml @@ -11,8 +11,8 @@ android:autoMirrored="true" tools:ignore="VectorRaster" > - + - + \ No newline at end of file diff --git a/app/src/main/res/layout-w900dp/bookmarks_drawer.xml b/app/src/main/res/layout-w900dp/bookmarks_drawer.xml index e5e9d919..2b7073ef 100644 --- a/app/src/main/res/layout-w900dp/bookmarks_drawer.xml +++ b/app/src/main/res/layout-w900dp/bookmarks_drawer.xml @@ -1,22 +1,22 @@ + You should have received a copy of the GNU General Public License + along with Privacy Browser. If not, see . --> . --> android:id="@+id/bookmarks_title_textview" android:layout_height="wrap_content" android:layout_width="match_parent" - android:paddingTop="35dp" - android:paddingBottom="8dp" - android:paddingStart="15dp" - android:paddingEnd="35dp" android:textStyle="bold" android:textSize="20sp" android:background="?attr/navigationHeaderBackground" diff --git a/app/src/main/res/layout-w900dp/domains_list_fragment.xml b/app/src/main/res/layout-w900dp/domains_list_fragment.xml index 2dbb6980..43d01599 100644 --- a/app/src/main/res/layout-w900dp/domains_list_fragment.xml +++ b/app/src/main/res/layout-w900dp/domains_list_fragment.xml @@ -1,6 +1,6 @@ - + + You should have received a copy of the GNU General Public License + along with Privacy Browser. If not, see . -->