Add a logcat activity. https://redmine.stoutner.com/issues/264
authorSoren Stoutner <soren@stoutner.com>
Wed, 13 Feb 2019 04:33:26 +0000 (21:33 -0700)
committerSoren Stoutner <soren@stoutner.com>
Wed, 13 Feb 2019 04:33:26 +0000 (21:33 -0700)
71 files changed:
.idea/assetWizardSettings.xml
.idea/dictionaries/soren.xml
app/src/main/AndroidManifest.xml
app/src/main/assets/de/about_licenses_dark.html
app/src/main/assets/de/about_licenses_light.html
app/src/main/assets/en/about_licenses_dark.html
app/src/main/assets/en/about_licenses_light.html
app/src/main/assets/es/about_licenses_dark.html
app/src/main/assets/es/about_licenses_light.html
app/src/main/assets/es/guide_ssl_certificates_dark.html
app/src/main/assets/es/guide_ssl_certificates_light.html
app/src/main/assets/it/about_licenses_dark.html
app/src/main/assets/it/about_licenses_light.html
app/src/main/assets/it/guide_ssl_certificates_dark.html
app/src/main/assets/it/guide_ssl_certificates_light.html
app/src/main/assets/ru/about_licenses_dark.html
app/src/main/assets/ru/about_licenses_light.html
app/src/main/assets/ru/guide_ssl_certificates_dark.html
app/src/main/assets/ru/guide_ssl_certificates_light.html
app/src/main/assets/shared_images/file_copy_dark.png [new file with mode: 0644]
app/src/main/assets/shared_images/file_copy_light.png [new file with mode: 0644]
app/src/main/assets/shared_images/save_dark.png [new file with mode: 0644]
app/src/main/assets/shared_images/save_light.png [new file with mode: 0644]
app/src/main/assets/tr/about_licenses_dark.html
app/src/main/assets/tr/about_licenses_light.html
app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveLogcatDialog.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java [new file with mode: 0644]
app/src/main/res/drawable/bug.xml [new file with mode: 0644]
app/src/main/res/drawable/clear_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/clear_light.xml [new file with mode: 0644]
app/src/main/res/drawable/cookies_warning.xml
app/src/main/res/drawable/copy_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/copy_light.xml [new file with mode: 0644]
app/src/main/res/drawable/save_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/save_dialog_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/save_dialog_light.xml [new file with mode: 0644]
app/src/main/res/drawable/save_light.xml [new file with mode: 0644]
app/src/main/res/drawable/select_all_dark.xml
app/src/main/res/drawable/select_all_light.xml
app/src/main/res/drawable/ssl_certificate_enabled_dark.xml
app/src/main/res/drawable/ssl_certificate_enabled_light.xml
app/src/main/res/layout-w900dp/bookmarks_drawer.xml
app/src/main/res/layout-w900dp/domains_list_fragment.xml
app/src/main/res/layout/appbar_spinner_item.xml
app/src/main/res/layout/bookmarks_drawer.xml
app/src/main/res/layout/import_export_coordinatorlayout.xml
app/src/main/res/layout/logcat_coordinatorlayout.xml [new file with mode: 0644]
app/src/main/res/layout/navigation_header.xml
app/src/main/res/layout/requests_appbar_spinner_dropdown_item.xml [new file with mode: 0644]
app/src/main/res/layout/requests_appbar_spinner_item.xml [new file with mode: 0644]
app/src/main/res/layout/requests_coordinatorlayout.xml
app/src/main/res/layout/save_logcat_dialog.xml [new file with mode: 0644]
app/src/main/res/menu/logcat_options_menu.xml [new file with mode: 0644]
app/src/main/res/menu/webview_navigation_menu.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values-tr/strings.xml
app/src/main/res/values-v21/styles.xml
app/src/main/res/values/attrs.xml
app/src/main/res/values/strings.xml
app/src/main/res/values/styles.xml

index e167cf29707abfad4f5519d099b7cb004f8288fa..7958d0d2fe07baf8bb810153f5265d81459398b0 100644 (file)
@@ -68,7 +68,7 @@
                                 <PersistentState>
                                   <option name="values">
                                     <map>
-                                      <entry key="url" value="jar:file:/home/soren/Android/android-studio/plugins/android/lib/android.jar!/images/material_design_icons/content/ic_sort_black_24dp.xml" />
+                                      <entry key="url" value="jar:file:/home/soren/Android/android-studio/plugins/android/lib/android.jar!/images/material_design_icons/content/ic_save_black_24dp.xml" />
                                     </map>
                                   </option>
                                 </PersistentState>
                         </option>
                         <option name="values">
                           <map>
-                            <entry key="assetSourceType" value="FILE" />
                             <entry key="autoMirrored" value="true" />
-                            <entry key="outputName" value="sort_selected_light" />
-                            <entry key="sourceFile" value="$USER_HOME$/ownCloud/Android/Privacy Browser/Icons/Icons/sort_selected_light.svg" />
+                            <entry key="outputName" value="save_dark" />
+                            <entry key="sourceFile" value="$USER_HOME$/ownCloud/Android/Privacy Browser/Icons/Icons/file_copy_light.svg" />
                           </map>
                         </option>
                       </PersistentState>
index 87f9d7ec3f4dd31621e05fd9602c1071eb95044c..e0dfdc07ce6af732496d21bf276ee8725a3d7228 100644 (file)
@@ -93,6 +93,7 @@
       <w>licensors</w>
       <w>linearlayout</w>
       <w>listview</w>
+      <w>logcats</w>
       <w>logins</w>
       <w>lossless</w>
       <w>macos</w>
index c018200ef5cf5458f9af38e8a9e4b6c17936be73..9e443d8fe7447ac3b98c621887cc9706ad3802b5 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
              `tools:ignore="unusedAttribute"` removes the lint warning that `persistableMode` does not apply to API < 21. -->
         <activity
             android:name=".activities.SettingsActivity"
-            android:label="@string/privacy_browser_settings"
+            android:label="@string/settings"
             android:parentActivityName=".activities.MainWebViewActivity"
             android:configChanges="orientation|screenSize"
             android:screenOrientation="fullUser"
             android:persistableMode="persistNever"
             tools:ignore="UnusedAttribute" />
 
+        <!-- `android:configChanges="orientation|screenSize"` makes the activity not reload when the orientation changes.
+             `android:persistableMode="persistNever"` removes Privacy Browser from the recent apps list on a device reboot.
+             `tools:ignore="unusedAttribute"` removes the lint warning that `persistableMode` does not apply to API < 21. -->
+        <activity
+            android:name=".activities.LogcatActivity"
+            android:label="@string/logcat"
+            android:parentActivityName=".activities.MainWebViewActivity"
+            android:configChanges="orientation|screenSize"
+            android:screenOrientation="fullUser"
+            android:persistableMode="persistNever"
+            tools:ignore="UnusedAttribute" />
+
         <!-- `android:configChanges="orientation|screenSize"` makes the activity not reload when the orientation changes.
              `android:persistableMode="persistNever"` removes Privacy Browser from the recent apps list on a device reboot.
              `tools:ignore="unusedAttribute"` removes the lint warning that `persistableMode` does not apply to API < 21. -->
         <activity
             android:name=".activities.GuideActivity"
-            android:label="@string/privacy_browser_guide"
+            android:label="@string/guide"
             android:parentActivityName=".activities.MainWebViewActivity"
             android:configChanges="orientation|screenSize"
             android:screenOrientation="fullUser"
index 18b24f36515b6df756d3f9ee01ef3a92a72911c8..26e7b4bd681d4c6e202af001bd0558825872766d 100644 (file)
         <p><img class="icon" src="../shared_images/edit_dark.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_dark.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_dark.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_dark.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_dark.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_dark.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_dark.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_dark.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_dark.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_dark.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_dark.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_dark.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_dark.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_dark.png"> settings.</p>
index 74116fa6b9a9e598dde8ea0e8b313fc89bb46d06..6592f4f3c1b340ead0b9491521a11e9b5b1ae687 100644 (file)
         <p><img class="icon" src="../shared_images/edit_light.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_light.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_light.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_light.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_light.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_light.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_light.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_light.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_light.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_light.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_light.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_light.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_light.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_light.png"> settings.</p>
index 99cb2c6e76a4241d11975f970366edf25c4181d5..da0defc463cbf2a7de5a146e450fbd891b6c1953 100644 (file)
         <p><img class="icon" src="../shared_images/edit_dark.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_dark.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_dark.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_dark.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_dark.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_dark.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_dark.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_dark.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_dark.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_dark.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_dark.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_dark.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_dark.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_dark.png"> settings.</p>
index 8fd7c674158ea75662b4ef383c66cfcd7ad86d5b..5ecac0a970081cf385a6eae215eea8dad93dd46c 100644 (file)
         <p><img class="icon" src="../shared_images/edit_light.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_light.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_light.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_light.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_light.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_light.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_light.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_light.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_light.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_light.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_light.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_light.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_light.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_light.png"> settings.</p>
index 2f92b5638faf96f12d442419b3d4e098128d4e67..1c36c9797a1eb707fd7c039536fbb791bbb68df6 100644 (file)
         <p><img class="icon" src="../shared_images/edit_dark.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_dark.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_dark.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_dark.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_dark.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_dark.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_dark.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_dark.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_dark.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_dark.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_dark.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_dark.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_dark.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_dark.png"> settings.</p>
index 1a3aa3125f9ec83ca980472367ad4b7ce087950e..1c8476f2e866e3671e0a3346c877afd4403403b2 100644 (file)
         <p><img class="icon" src="../shared_images/edit_light.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_light.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_light.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_light.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_light.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_light.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_light.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_light.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_light.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_light.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_light.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_light.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_light.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_light.png"> settings.</p>
index 1780234d76a38ff5fb506c87d5a65856d68237a0..9a704795ed8809795fb13f924a983c947545c63f 100644 (file)
@@ -41,8 +41,8 @@
 
         <p>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.</p>
+            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.</p>
 
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
index 20097fabce9d4f583c03ca38f558f5c3ee4b71b1..02dfecb1b65f3e858192b684c75ae7589cf93390 100644 (file)
@@ -41,8 +41,8 @@
 
         <p>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.</p>
+            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.</p>
 
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
index caba0d32c9d687eca843293e355cd9fc35bd66cd..695c33dd9eadb1f81fe93b5cb7b638021f2b90ee 100644 (file)
         <p><img class="icon" src="../shared_images/edit_dark.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_dark.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_dark.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_dark.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_dark.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_dark.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_dark.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_dark.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_dark.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_dark.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_dark.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_dark.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_dark.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_dark.png"> settings.</p>
index 3b92019efdbbc7a4d1bd1bda491378e559ad041f..f8698477c677c0ba2d49d1970524b3c2aca7d118 100644 (file)
         <p><img class="icon" src="../shared_images/edit_light.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_light.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_light.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_light.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_light.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_light.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_light.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_light.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_light.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_light.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_light.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_light.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_light.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_light.png"> settings.</p>
index b9cdc1b97ea908da4b562a3525cbc829b41d988b..f671363132ae39ac8a3ce6ec57e289bf28851672 100644 (file)
@@ -42,7 +42,7 @@
         <p>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.</p>
+            Privacy Browser permette anche di appuntare gli indirizzi IP.</p>
 
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
index 98da47b6ce3d1bff7d7ec991f75ba66ad31b0989..88b6e16e95ed9f48abd043e3a2fac32e32bc5855 100644 (file)
@@ -42,7 +42,7 @@
         <p>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.</p>
+            Privacy Browser permette anche di appuntare gli indirizzi IP.</p>
 
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
index d2ed014e24a74c35a833c5ead3c64ed6ec340f1e..fb207f621c016a16b422204ca40a7c734e8e1275 100644 (file)
         <p><img class="icon" src="../shared_images/edit_dark.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_dark.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_dark.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_dark.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_dark.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_dark.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_dark.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_dark.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_dark.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_dark.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_dark.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_dark.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_dark.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_dark.png"> settings.</p>
index 54089956eb13e56856076f14c36b949f619b5360..9770ee6d90bbe10ca9b94462b07467703d8e18d2 100644 (file)
         <p><img class="icon" src="../shared_images/edit_light.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_light.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_light.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_light.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_light.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_light.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_light.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_light.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_light.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_light.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_light.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_light.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_light.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_light.png"> settings.</p>
index dcff620dc60e445c8aadf53689069cfa95b367be..9cb32b4bd355a247daeae4e1a8e65528e7231cd8 100644 (file)
@@ -40,8 +40,8 @@
 
         <p>Срок действия сертификатов SSL истекает в указанную дату, поэтому даже закрепленные сертификаты SSL будут периодически обновляться.
             Как правило, закрепление сертификатов SSL в большинстве случаев не требуется.
-            Ð\9dо Ð´Ð»Ñ\8f Ñ\82еÑ\85, ÐºÑ\82о Ð¿Ð¾Ð´Ð¾Ð·Ñ\80еваеÑ\82, Ñ\87Ñ\82о Ð·Ð° Ð½Ð¸Ð¼Ð¸ Ð²ÐµÐ´ÐµÑ\82Ñ\81Ñ\8f Ð½Ð°Ð±Ð»Ñ\8eдение, Ð·Ð°ÐºÑ\80епление Ñ\81еÑ\80Ñ\82иÑ\84икаÑ\82а SSL может обнаружить и помешать атаке MITM.
-            Privacy Browser also has the ability to pin IP addresses.</p>
+            Ð\9dо Ð´Ð»Ñ\8f Ñ\82ого, ÐºÑ\82о Ð¿Ð¾Ð´Ð¾Ð·Ñ\80еваеÑ\82, Ñ\87Ñ\82о Ð·Ð° Ð½Ð¸Ð¼ Ð²ÐµÐ´ÐµÑ\82Ñ\81Ñ\8f Ð½Ð°Ð±Ð»Ñ\8eдение, Ð·Ð°ÐºÑ\80епление Ñ\81еÑ\80Ñ\82иÑ\84икаÑ\82а SSL Ð¿Ð¾может обнаружить и помешать атаке MITM.
+            Privacy Browser также имеет возможность закрепления IP-адресов.</p>
 
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
index 618860e2a4640381fc846dacd827740a5847c0d0..e566b4ebe65e85e2b9759c9ce1137a0e814dbb89 100644 (file)
@@ -40,8 +40,8 @@
 
         <p>Срок действия сертификатов SSL истекает в указанную дату, поэтому даже закрепленные сертификаты SSL будут периодически обновляться.
             Как правило, закрепление сертификатов SSL в большинстве случаев не требуется.
-            Ð\9dо Ð´Ð»Ñ\8f Ñ\82еÑ\85, ÐºÑ\82о Ð¿Ð¾Ð´Ð¾Ð·Ñ\80еваеÑ\82, Ñ\87Ñ\82о Ð·Ð° Ð½Ð¸Ð¼Ð¸ Ð²ÐµÐ´ÐµÑ\82Ñ\81Ñ\8f Ð½Ð°Ð±Ð»Ñ\8eдение, Ð·Ð°ÐºÑ\80епление Ñ\81еÑ\80Ñ\82иÑ\84икаÑ\82а SSL может обнаружить и помешать атаке MITM.
-            Privacy Browser also has the ability to pin IP addresses.</p>
+            Ð\9dо Ð´Ð»Ñ\8f Ñ\82ого, ÐºÑ\82о Ð¿Ð¾Ð´Ð¾Ð·Ñ\80еваеÑ\82, Ñ\87Ñ\82о Ð·Ð° Ð½Ð¸Ð¼ Ð²ÐµÐ´ÐµÑ\82Ñ\81Ñ\8f Ð½Ð°Ð±Ð»Ñ\8eдение, Ð·Ð°ÐºÑ\80епление Ñ\81еÑ\80Ñ\82иÑ\84икаÑ\82а SSL Ð¿Ð¾может обнаружить и помешать атаке MITM.
+            Privacy Browser также имеет возможность закрепления IP-адресов.</p>
 
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
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 (file)
index 0000000..b0aa33e
Binary files /dev/null and b/app/src/main/assets/shared_images/file_copy_dark.png differ
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 (file)
index 0000000..2b8a684
Binary files /dev/null and b/app/src/main/assets/shared_images/file_copy_light.png differ
diff --git a/app/src/main/assets/shared_images/save_dark.png b/app/src/main/assets/shared_images/save_dark.png
new file mode 100644 (file)
index 0000000..4b39ab0
Binary files /dev/null and b/app/src/main/assets/shared_images/save_dark.png differ
diff --git a/app/src/main/assets/shared_images/save_light.png b/app/src/main/assets/shared_images/save_light.png
new file mode 100644 (file)
index 0000000..a415690
Binary files /dev/null and b/app/src/main/assets/shared_images/save_light.png differ
index 99cb2c6e76a4241d11975f970366edf25c4181d5..da0defc463cbf2a7de5a146e450fbd891b6c1953 100644 (file)
         <p><img class="icon" src="../shared_images/edit_dark.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_dark.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_dark.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_dark.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_dark.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_dark.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_dark.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_dark.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_dark.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_dark.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_dark.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_dark.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_dark.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_dark.png"> settings.</p>
index 8fd7c674158ea75662b4ef383c66cfcd7ad86d5b..5ecac0a970081cf385a6eae215eea8dad93dd46c 100644 (file)
         <p><img class="icon" src="../shared_images/edit_light.png"> edit.</p>
         <p><img class="icon" src="../shared_images/expand_less_light.png"> expand_less.</p>
         <p><img class="icon" src="../shared_images/expand_more_light.png"> expand_more.</p>
+        <p><img class="icon" src="../shared_images/file_copy_light.png"> file_copy.</p>
         <p><img class="icon" src="../shared_images/file_download_light.png"> file_download.</p>
         <p><img class="icon" src="../shared_images/find_in_page_light.png"> find_in_page.</p>
         <p><img class="icon" src="../shared_images/folder_light.png"> folder.</p>
         <p><img class="icon" src="../shared_images/new_releases_light.png"> new releases.</p>
         <p><img class="icon" src="../shared_images/question_answer_light.png"> question_answer.</p>
         <p><img class="icon" src="../shared_images/refresh_light.png"> refresh.</p>
+        <p><img class="icon" src="../shared_images/save_light.png"> save.</p>
         <p><img class="icon" src="../shared_images/search_light.png"> search.</p>
         <p><img class="icon" src="../shared_images/select_all_light.png"> select_all.</p>
         <p><img class="icon" src="../shared_images/settings_light.png"> settings.</p>
index a2d1797558bb86a9420e00728c6e25852ac13c72..80a3a98f8e5b059b9b924ea969c1898499935cd7 100644 (file)
@@ -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 (file)
index 0000000..32d1bf4
--- /dev/null
@@ -0,0 +1,448 @@
+/*
+ * Copyright © 2019 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.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<Void, Void, String> {
+        // Create a weak reference to the calling activity.
+        private final WeakReference<Activity> 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
index 65528ebf25d8b6cf222df1599154254e0f10d732..868b6d71a7c78b033e8980858410207a0e362755 100644 (file)
@@ -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.
                     }
                 }
index 71ce3070ab557d01f92f037b3f7d493de3df9539..098fd66fb048521c4fe2d116fa579667c5a5eafe 100644 (file)
@@ -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);
index 8e7f5e968534dc591105ba5b9bd6557303a5b6c1..62c704de7728db11a0d78f9a4a2b13a61b94fc24 100644 (file)
@@ -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<String, Void, SpannableStringBuilder[]> {
         // Create a weak reference to the calling activity.
         private WeakReference<Activity> 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.
index 0bb5f9dae7fb7adcabc5281bdc94851b9a000f03..64efa611a134d622d594d07413df36100fe4003a 100644 (file)
@@ -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/ImportExportStoragePermissionDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java
deleted file mode 100644 (file)
index 447b67e..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright © 2018 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.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;
-import android.view.WindowManager;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.activities.MainWebViewActivity;
-
-public class ImportExportStoragePermissionDialog extends DialogFragment {
-    // The listener is used in `onAttach()` and `onCreateDialog()`.
-    private ImportExportStoragePermissionDialogListener importExportStoragePermissionDialogListener;
-
-    // The public interface is used to send information back to the parent activity.
-    public interface ImportExportStoragePermissionDialogListener {
-        void onCloseImportExportStoragePermissionDialog();
-    }
-
-    @Override
-    public void onAttach(Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for the listener from the launching context.
-        importExportStoragePermissionDialogListener = (ImportExportStoragePermissionDialogListener) context;
-    }
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Use a builder to create the alert dialog.
-        AlertDialog.Builder dialogBuilder;
-
-        // Set the style and the icon according to the theme.
-        if (MainWebViewActivity.darkTheme) {
-            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
-            dialogBuilder.setIcon(R.drawable.import_export_dark);
-        } else {
-            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
-            dialogBuilder.setIcon(R.drawable.import_export_light);
-        }
-
-        // Set the title.
-        dialogBuilder.setTitle(R.string.storage_permission);
-
-        // Set the text.
-        dialogBuilder.setMessage(R.string.storage_permission_message);
-
-        // 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();
-        });
-
-        // Create an alert dialog from the builder.
-        final AlertDialog alertDialog = dialogBuilder.create();
-
-        // Disable screenshots if not allowed.
-        if (!MainWebViewActivity.allowScreenshots) {
-            // Remove the warning below that `getWindow()` might be null.
-            assert alertDialog.getWindow() != null;
-
-            // Disable screenshots.
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // `onCreateDialog()` requires the return of an `AlertDialog`.
-        return alertDialog;
-    }
-}
index 2d506dc4c304eca6bcb3f4076f45ac2548f33cb8..0a25771dfbb994c4605559a31ccc61a004bbf3e9 100644 (file)
@@ -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 (file)
index 0000000..4faafcb
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Copyright © 2016-2019 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.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/StoragePermissionDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/StoragePermissionDialog.java
new file mode 100644 (file)
index 0000000..ed11959
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2018 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.dialogs;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+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 StoragePermissionDialog extends AppCompatDialogFragment {
+    // The listener is used in `onAttach()` and `onCreateDialog()`.
+    private StoragePermissionDialogListener storagePermissionDialogListener;
+
+    // The public interface is used to send information back to the parent activity.
+    public interface StoragePermissionDialogListener {
+        void onCloseStoragePermissionDialog();
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        // Run the default commands.
+        super.onAttach(context);
+
+        // Get a handle for the listener from the launching context.
+        storagePermissionDialogListener = (StoragePermissionDialogListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use a builder to create the alert dialog.
+        AlertDialog.Builder dialogBuilder;
+
+        // Set the style and the icon according to the theme.
+        if (MainWebViewActivity.darkTheme) {
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
+            dialogBuilder.setIcon(R.drawable.import_export_dark);
+        } else {
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
+            dialogBuilder.setIcon(R.drawable.import_export_light);
+        }
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.storage_permission);
+
+        // Set the text.
+        dialogBuilder.setMessage(R.string.storage_permission_message);
+
+        // 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.
+            storagePermissionDialogListener.onCloseStoragePermissionDialog();
+        });
+
+        // Create an alert dialog from the builder.
+        final AlertDialog alertDialog = dialogBuilder.create();
+
+        // Disable screenshots if not allowed.
+        if (!MainWebViewActivity.allowScreenshots) {
+            // Remove the warning below that `getWindow()` might be null.
+            assert alertDialog.getWindow() != null;
+
+            // Disable screenshots.
+            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        }
+
+        // `onCreateDialog()` requires the return of an `AlertDialog`.
+        return alertDialog;
+    }
+}
diff --git a/app/src/main/res/drawable/bug.xml b/app/src/main/res/drawable/bug.xml
new file mode 100644 (file)
index 0000000..c21c2eb
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- `bug.xml` comes from the Android Material icon set, where it is called `bug_report`.  It is released under the Apache License 2.0. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` can be used. -->
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
+</vector>
\ 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 (file)
index 0000000..738b53f
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- `clear_dark.xml` comes from the Android Material icon set, where it is called `close`.  It is released under the Apache License 2.0. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` can be used. -->
+    <path
+        android:fillColor="#FFE0E0E0"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
+</vector>
\ 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 (file)
index 0000000..a30170d
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- `clear_light.xml` comes from the Android Material icon set, where it is called `close`.  It is released under the Apache License 2.0. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` can be used. -->
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
+</vector>
\ No newline at end of file
index 733448f6d12db3f996f9896858ba53eb0031b3a7..6f497590189a246c13bb0a3ca5e507f4bc575847 100644 (file)
@@ -11,7 +11,7 @@
     android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <!-- We have to use a hard coded color until the minimum API >= 21.  Then we can use `@color`. -->
     <path
         android:fillColor="#FFB71C1C"
         android:pathData="M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A9,9 0 0,0 21,12C21,11.5 20.96,11 20.87,10.5C20.6,10 20,10 20,10H18V9C18,8 17,8 17,8H15V7C15,6 14,6 14,6H13V4C13,3 12,3 12,3M9.5,6A1.5,1.5 0 0,1 11,7.5A1.5,1.5 0 0,1 9.5,9A1.5,1.5 0 0,1 8,7.5A1.5,1.5 0 0,1 9.5,6M6.5,10A1.5,1.5 0 0,1 8,11.5A1.5,1.5 0 0,1 6.5,13A1.5,1.5 0 0,1 5,11.5A1.5,1.5 0 0,1 6.5,10M11.5,11A1.5,1.5 0 0,1 13,12.5A1.5,1.5 0 0,1 11.5,14A1.5,1.5 0 0,1 10,12.5A1.5,1.5 0 0,1 11.5,11M16.5,13A1.5,1.5 0 0,1 18,14.5A1.5,1.5 0 0,1 16.5,16H16.5A1.5,1.5 0 0,1 15,14.5H15A1.5,1.5 0 0,1 16.5,13M11,16A1.5,1.5 0 0,1 12.5,17.5A1.5,1.5 0 0,1 11,19A1.5,1.5 0 0,1 9.5,17.5A1.5,1.5 0 0,1 11,16Z" />
diff --git a/app/src/main/res/drawable/copy_dark.xml b/app/src/main/res/drawable/copy_dark.xml
new file mode 100644 (file)
index 0000000..f37115a
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `copy_dark.xml` comes from the Android Material icon set, where it is called `file_copy`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FFE0E0E0"
+        android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM15,5l6,6v10c0,1.1 -0.9,2 -2,2L7.99,23C6.89,23 6,22.1 6,21l0.01,-14c0,-1.1 0.89,-2 1.99,-2h7zM14,12h5.5L14,6.5L14,12z"/>
+</vector>
\ 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 (file)
index 0000000..1c2dc99
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `copy_light.xml` comes from the Android Material icon set, where it is called `file_copy`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM15,5l6,6v10c0,1.1 -0.9,2 -2,2L7.99,23C6.89,23 6,22.1 6,21l0.01,-14c0,-1.1 0.89,-2 1.99,-2h7zM14,12h5.5L14,6.5L14,12z"/>
+</vector>
\ 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 (file)
index 0000000..e26d3bf
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `save_dark.xml` comes from the Android Material icon set, where it is called `save`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FFE0E0E0"
+        android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
+</vector>
\ 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 (file)
index 0000000..f49b8ab
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `save_dialog_dark.xml` comes from the Android Material icon set, where it is called `save`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF1E88E5"
+        android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
+</vector>
\ 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 (file)
index 0000000..e4592c8
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `save_dialog_light.xml` comes from the Android Material icon set, where it is called `save`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF1565C0"
+        android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
+</vector>
\ 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 (file)
index 0000000..df119f5
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `save_light.xml` comes from the Android Material icon set, where it is called `save`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
+</vector>
\ No newline at end of file
index 6f3fad01b927b66ec95c690cafd944c31ec44cad..61008a291bc7fe2bfafb0b1e88605c93c3a97e4a 100644 (file)
@@ -6,8 +6,8 @@
     android:viewportHeight="24.0"
     android:viewportWidth="24.0" >
 
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
     <path
         android:fillColor="#FFE0E0E0"
         android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
-</vector>
+</vector>
\ No newline at end of file
index 590eca0845f8276ca9750a2dc02b6d2ad6f0d51a..e5ed2b1312a2fdbce11b62657984dca0466a0e91 100644 (file)
@@ -6,8 +6,8 @@
     android:viewportHeight="24.0"
     android:viewportWidth="24.0" >
 
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
-</vector>
+</vector>
\ No newline at end of file
index d0ff3a30dd0ae63668f84f796daa118704ce478e..9d5436c14ff7781a9149d143724213eb8651dedf 100644 (file)
@@ -11,8 +11,8 @@
     android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
     <path
         android:fillColor="#FF1E88E5"
         android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5L17,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L23,5c0,-0.55 -0.45,-1 -1,-1zM21.2,4h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L21.2,4zM18.92,12c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L7,13v-2h2c0.55,0 1,-0.45 1,-1L10,8h2c1.1,0 2,-0.9 2,-2L14,3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 10,-4.48 10,-10c0,-0.34 -0.02,-0.67 -0.05,-1h-2.03zM10,20.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L8,16v1c0,1.1 0.9,2 2,2v1.93z" />
-</vector>
+</vector>
\ No newline at end of file
index 42407ce7fadb9848fa44bad9c91f79558c5309a6..80d5541df3615a218ee6e4c5f23a330a444498a1 100644 (file)
@@ -11,8 +11,8 @@
     android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` may be used. -->
     <path
         android:fillColor="#FF1565C0"
         android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5L17,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L23,5c0,-0.55 -0.45,-1 -1,-1zM21.2,4h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L21.2,4zM18.92,12c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L7,13v-2h2c0.55,0 1,-0.45 1,-1L10,8h2c1.1,0 2,-0.9 2,-2L14,3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 10,-4.48 10,-10c0,-0.34 -0.02,-0.67 -0.05,-1h-2.03zM10,20.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L8,16v1c0,1.1 0.9,2 2,2v1.93z" />
-</vector>
+</vector>
\ No newline at end of file
index e5e9d919f4d63c8b0f2242db9e643a489220e279..2b7073efc44d1eb1e53396200bf4402906cb558d 100644 (file)
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2017,2019 Soren Stoutner <soren@stoutner.com>.
 
-This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>>.
+  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 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.
+  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/>. -->
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
 <!-- `android:layout_width="400dp"` keeps the bookmarks drawer from filling the whole screen on a tablet. -->
 <FrameLayout
@@ -35,10 +35,6 @@ along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
             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"
index 2dbb6980a7f8338629c69d66ea1457e23bb76276..43d015992080ba04a86f27c83218e7133aa40930 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2017,2019 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -17,7 +17,7 @@
   You should have received a copy of the GNU General Public License
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<!-- `android:choiceMode="singleChoice"` allows a selected domain to be highlighted.  `android:dividerHeight` must be specified with `android:divider` or the height will be `0dp`.  In our case we want the height to be `0dp`. -->
+<!-- `android:choiceMode="singleChoice"` allows a selected domain to be highlighted.-->
 <tools:ListView
     android:id="@+id/domains_listview"
     xmlns:android="http://schemas.android.com/apk/res/android"
index 5c61a20803e360a24b7641a88c97675ce49a70c1..3b79130b28f7fc7cccb118626ded0e7d656ad0cd 100644 (file)
@@ -35,7 +35,6 @@
         tools:ignore="contentDescription" />
 
     <TextView
-        xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/spinner_item_textview"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
index c36bd3c13bd5e10734e584a24477338a23614238..6b574781a9969c8dbe29aebfe95399f63615f765 100644 (file)
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
 
-This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>>.
+  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 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.
+  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/>. -->
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
index 67b0cfd759de541c22fdea3a8b3def9a859db6aa..c7dbc8101e7efc53a0c40364befbcc71375e3eea 100644 (file)
                             </android.support.design.widget.TextInputLayout>
 
                             <Button
-                                android:id="@+id/browser_button"
+                                android:id="@+id/browse_button"
                                 android:layout_height="wrap_content"
                                 android:layout_width="wrap_content"
                                 android:layout_gravity="center_vertical"
diff --git a/app/src/main/res/layout/logcat_coordinatorlayout.xml b/app/src/main/res/layout/logcat_coordinatorlayout.xml
new file mode 100644 (file)
index 0000000..ac2400a
--- /dev/null
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2018-2019 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/>. -->
+
+<!-- `android:fitsSystemWindows="true"` moves the AppBar below the status bar.
+    When it is specified the theme should include `<item name="android:windowTranslucentStatus">true</item>` to make the status bar a transparent, darkened overlay. -->
+<android.support.design.widget.CoordinatorLayout
+    android:id="@+id/logcat_coordinatorlayout"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:fitsSystemWindows="true" >
+
+    <!-- the LinearLayout with `orientation="vertical"` moves the content below the AppBarLayout. -->
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="vertical" >
+
+        <android.support.design.widget.AppBarLayout
+            android:id="@+id/logcat_appbarlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent" >
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/logcat_toolbar"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:background="?attr/colorPrimaryDark"
+                android:theme="?attr/appBarTextTheme"
+                app:popupTheme="?attr/popupsTheme" />
+        </android.support.design.widget.AppBarLayout>
+
+        <android.support.v4.widget.SwipeRefreshLayout
+            android:id="@+id/logcat_swiperefreshlayout"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent" >
+
+            <ScrollView
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent" >
+
+                <TextView
+                    android:id="@+id/logcat_textview"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:layout_margin="10dp"
+                    android:textIsSelectable="true" />
+            </ScrollView>
+        </android.support.v4.widget.SwipeRefreshLayout>
+    </LinearLayout>
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
index fe46e20e150b2d08b7cc31d21d052ff37aec8aed..3eb496d195c8331f8868864477515c53cffcd45c 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2016-2017 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
diff --git a/app/src/main/res/layout/requests_appbar_spinner_dropdown_item.xml b/app/src/main/res/layout/requests_appbar_spinner_dropdown_item.xml
new file mode 100644 (file)
index 0000000..6c9fd9d
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2017-2019 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/>. -->
+
+<!-- A checked text view allows the color of the text to be changed when it is selected (checked). -->
+<CheckedTextView
+    android:id="@+id/spinner_item_textview"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:maxLines="1"
+    android:ellipsize="end"
+    android:paddingStart="20dp"
+    android:paddingEnd="20dp"
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp"
+    android:textSize="18sp"
+    android:textColor="?attr/appbarSpinnerTextColorSelector"
+    android:background="?attr/spinnerBackground" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/requests_appbar_spinner_item.xml b/app/src/main/res/layout/requests_appbar_spinner_item.xml
new file mode 100644 (file)
index 0000000..459f854
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2017-2019 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/>. -->
+
+<TextView
+    android:id="@+id/spinner_item_textview"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:maxLines="1"
+    android:ellipsize="end"
+    android:paddingStart="10dp"
+    android:paddingEnd="10dp"
+    android:textSize="18sp"
+    android:textColor="?attr/spinnerHeaderTextColor" />
\ No newline at end of file
index 126fd5fa9755bca8e39f5ca57a87150aa4822316..510c0a360720f17a6e3825ad87eb14e189f1e31d 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2018 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2018-2019 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -26,9 +26,9 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
-    android:fitsSystemWindows="true">
+    android:fitsSystemWindows="true" >
 
-    <!-- the `LinearLayout` with `orientation="vertical"` moves the content below the `AppBarLayout`. -->
+    <!-- the LinearLayout with `orientation="vertical"` moves the content below the AppBarLayout. -->
     <LinearLayout
         android:layout_height="match_parent"
         android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/save_logcat_dialog.xml b/app/src/main/res/layout/save_logcat_dialog.xml
new file mode 100644 (file)
index 0000000..6c359bf
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2019 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/>. -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent" >
+
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="vertical"
+        android:layout_marginTop="10dp"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp" >
+
+        <!-- Align the EditText and the select file button horizontally. -->
+        <LinearLayout
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="horizontal" >
+
+            <!-- `android.support.design.widget.TextInputLayout` makes the `android:hint` float above the `EditText`. -->
+            <android.support.design.widget.TextInputLayout
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1" >
+
+                <!-- `android:inputType="textUri" disables spell check and places an `/` on the main keyboard. -->
+                <android.support.design.widget.TextInputEditText
+                    android:id="@+id/file_name_edittext"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:hint="@string/file_name"
+                    android:inputType="textMultiLine|textUri" />
+            </android.support.design.widget.TextInputLayout>
+
+            <Button
+                android:id="@+id/browse_button"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/browse" />
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/storage_permission_textview"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/storage_permission_explanation"
+            android:textColor="?android:textColorPrimary"
+            android:textAlignment="center" />
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/menu/logcat_options_menu.xml b/app/src/main/res/menu/logcat_options_menu.xml
new file mode 100644 (file)
index 0000000..e2d1a60
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2015-2019 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/>. -->
+
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <!-- `android:iconTint` can be used once the minimum API >= 26 instead of including a separate drawable for each theme. -->
+    <item
+        android:id="@+id/copy"
+        android:title="@string/copy"
+        android:orderInCategory="10"
+        android:icon="?attr/copyIcon"
+        app:showAsAction="always" />
+
+    <!-- `android:iconTint` can be used once the minimum API >= 26 instead of including a separate drawable for each theme. -->
+    <item
+        android:id="@+id/save"
+        android:title="@string/save"
+        android:orderInCategory="20"
+        android:icon="?attr/saveIcon"
+        app:showAsAction="ifRoom" />
+
+    <!-- `android:iconTint` can be used once the minimum API >= 26 instead of including a separate drawable for each theme. -->
+    <item
+        android:id="@+id/clear"
+        android:title="@string/clear"
+        android:orderInCategory="30"
+        android:icon="?attr/clearIcon"
+        app:showAsAction="ifRoom" />
+</menu>
\ No newline at end of file
index ba07e1a163dec20027d80f97d0c611f0087c7d00..74a9249ce10361b91d80e675385e3e94bf8f218f 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2016-2017 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
             android:title="@string/import_export"
             android:icon="@drawable/import_export_light"
             android:orderInCategory="90" />
+
+        <item
+            android:id="@+id/logcat"
+            android:title="@string/logcat"
+            android:icon="@drawable/bug"
+            android:orderInCategory="100" />
     </group>
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
             android:id="@+id/guide"
             android:title="@string/guide"
             android:icon="@drawable/guide"
-            android:orderInCategory="100" />
+            android:orderInCategory="110" />
 
         <item
             android:id="@+id/about"
             android:title="@string/about"
             android:icon="@drawable/about_light"
-            android:orderInCategory="110" />
+            android:orderInCategory="120" />
     </group>
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
     <group
         android:id="@+id/exit_group" >
         <item
-            android:id="@+id/clearAndExit"
+            android:id="@+id/clear_and_exit"
             android:title="@string/clear_and_exit"
             android:icon="@drawable/open_with_external_app_enabled_light"
-            android:orderInCategory="120" />
+            android:orderInCategory="130" />
     </group>
 </menu>
\ No newline at end of file
index 33cdd2e639d395ccc60e8e28dbea58c8f64d2c6d..f9a7e66b53149742169fdf6aea78079f4e09ce37 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
 
   Translation 2018 Stefan Erhardt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
@@ -25,7 +25,6 @@
 <resources>
     <!-- Activities. -->
     <string name="privacy_browser">Privacy Browser</string>
-    <string name="privacy_browser_settings">Privacy Browser Einstellungen</string>
     <!-- For translations, `android_asset_path` should be the localization abbreviation.  This should not be translated unless the Guide and About sections are localized. -->
     <string name="android_asset_path">de</string>
 
         <string name="current_website_ssl_certificate">SSL-Zertifikat der aktuellen Webseite</string>
         <string name="load_an_encrypted_website">Zuerst verschlüsselte Webseite laden...</string>
 
+    <!-- Import/Export. -->
+    <string name="privacy_browser_settings_pbs">Privacy Browser Einstellungen.pbs</string>
+
     <!-- Guide. -->
-    <string name="privacy_browser_guide">Privacy Browser Handbuch</string>
     <string name="overview">Übersicht</string>
     <string name="local_storage">Lokale Speicherung</string>
     <string name="ssl_certificates">SSL-Zertifikate</string>
index 3392ebc38669643d02405d5e0955ca4bc8ed714a..f4d57e32b7ce13f4fb9281d408ae3286c7111cb4 100644 (file)
@@ -23,7 +23,6 @@
 <resources>
     <!-- Activities. -->
     <string name="privacy_browser">Navegador Privado</string>
-    <string name="privacy_browser_settings">Configuración de Navegador Privado</string>
     <!-- For translations, `android_asset_path` should be the localization abbreviation.  This should not be translated unless the Guide and About sections are localized. -->
     <string name="android_asset_path">es</string>
 
     <string name="kitkat_password_encryption_message">El cifrado de contraseñas no funciona en Android KitKat.</string>
     <string name="openkeychain_required">El cifrado OpenPGP requiere que esté instalado OpenKeychain.</string>
     <string name="openkeychain_import_instructions">El archivo sin cifrar tendrá que ser importado en un paso separado después de ser descifrado.</string>
+    <string name="privacy_browser_settings_pbs">Configuración de Navegador Privado.pbs</string>
     <string name="file_location">Ubicación del archivo</string>
     <string name="browse">Navegar</string>
     <string name="export">Exportar</string>
     <string name="export_successful">Exportación exitosa.</string>
     <string name="export_failed">Exportación fallida:</string>
     <string name="import_failed">Importación fallida:</string>
-    <string name="cannot_export">Los ajustes no pueden exportarse a esta ubicación porque no se ha concedido el permiso de almacenamiento.</string>
-    <string name="cannot_import">Los ajustes no pueden importarse desde esta ubicación porque no se ha concedido el permiso de almacenamiento.</string>
     <string name="invalid_location">no es una ubicación válida.</string>
     <string name="storage_permission">Permiso de almacenamiento</string>
     <string name="storage_permission_message">Navegador Privado necesita el permiso de almacenamiento para acceder a los directorios públicos.
         De lo contrario, sólo funcionarán los directorios de aplicaciones.</string>
 
     <!-- Guide. -->
-    <string name="privacy_browser_guide">Guía de Navegador Privado</string>
     <string name="overview">Visión general</string>
     <string name="local_storage">Almacenamiento local</string>
     <string name="ssl_certificates">Certificados SSL</string>
index edc20f923320e8eeb83db181a5e67e0dbc655ddd..5c6f51c782586c6cea234817db8365916e8cea22 100644 (file)
@@ -23,7 +23,6 @@
 <resources>
     <!-- Activities. -->
     <string name="privacy_browser">Privacy Browser</string>
-    <string name="privacy_browser_settings">Impostazioni</string>
     <!-- For translations, `android_asset_path` should be the localization abbreviation.  This should not be translated unless the Guide and About sections are localized. -->
     <string name="android_asset_path">it</string>
 
     <string name="export_successful">Esportazione riuscita</string>
     <string name="export_failed">Esportazione fallita:</string>
     <string name="import_failed">Importazione fallita:</string>
-    <string name="cannot_export">Le impostazioni non possono essere esportate in questa cartella perché non si è in possesso dei permessi di accesso alla memoria.</string>
-    <string name="cannot_import">Le impostazioni non possono essere importate in questa cartella perché non si è in possesso dei permessi di accesso alla memoria.</string>
     <string name="invalid_location">non è una cartella valida.</string>
     <string name="storage_permission">Permesso di accesso alla memoria</string>
     <string name="storage_permission_message">Privacy Browser necessita del permesso di accesso alla memoria per poter accedere alle cartelle pubbliche.
         altrimenti saranno accessibili solo le cartelle dell\'applicazione.</string>
 
     <!-- Guide. -->
-    <string name="privacy_browser_guide">Guida di Privacy Browser</string>
     <string name="overview">Descrizione</string>
     <string name="local_storage">Archiviazione Locale</string>
     <string name="ssl_certificates">Certificati SSL</string>
index 8a147b56fa5ccdb6cada36b94d3a346b0228bc20..0ae9442a5a2d6062aadd55be669ffe6efae973d0 100644 (file)
@@ -21,7 +21,6 @@
 <resources>
     <!-- Activities. -->
     <string name="privacy_browser">Privacy Browser</string>
-    <string name="privacy_browser_settings">Настройки Privacy Browser</string>
     <!-- For translations, `android_asset_path` should be the localization abbreviation.  This should not be translated unless the Guide and About sections are localized. -->
     <string name="android_asset_path">ru</string> -->
 
     <string name="kitkat_password_encryption_message">Шифрование паролем не работает на Android KitKat.</string>
     <string name="openkeychain_required">Для использования шифрования OpenPGP необходимо приложение OpenKeychain.</string>
     <string name="openkeychain_import_instructions">Незашифрованный файл должен быть импортирован на отдельном шаге после его дешифрования.</string>
+    <string name="privacy_browser_settings_pbs">Настройки Privacy Browser.pbs</string>
     <string name="file_location">Расположение файла</string>
     <string name="browse">Обзор</string>
     <string name="export">Экспорт</string>
     <string name="export_successful">Экспорт выполнен.</string>
     <string name="export_failed">Сбой при экспорте:</string>
     <string name="import_failed">Сбой при импорте:</string>
-    <string name="cannot_export">Настройки не могут быть экспортированы в это расположение, так как не было предоставлено разрешение на доступ к хранилищу.</string>
-    <string name="cannot_import">Настройки не могут быть импортированы из этого расположения, так как не было предоставлено разрешение на доступ к хранилищу.</string>
     <string name="invalid_location">- недопустимое расположение.</string>
     <string name="storage_permission">Доступ к хранилищу</string>
     <string name="storage_permission_message">Privacy Browser необходимо разрешение на доступ к внешним папкам. Если доступ предоставлен не будет, можно использовать локальную папку приложения.</string>
     <string name="storage_permission_explanation">Для доступа к файлам во внешних папках требуется соответствующее разрешение. В противном случае будут работать только локальные папки.</string>
 
     <!-- Guide. -->
-    <string name="privacy_browser_guide">Руководство по Privacy Browser</string>
     <string name="overview">Обзор</string>
     <string name="local_storage">Локальное хранилище</string>
     <string name="ssl_certificates">Сертификаты SSL</string>
index 0238433675126343c809d78f80546d8ce6c8dac4..2e3a46859cbdce4deb0ef64f7c421dee1a84479f 100644 (file)
@@ -21,7 +21,6 @@
 <resources>
     <!-- Activities. -->
     <string name="privacy_browser">Privacy Browser</string>
-    <string name="privacy_browser_settings">Privacy Browser Ayarları</string>
     <!-- For translations, `android_asset_path` should be the localization abbreviation.  This should not be translated unless the Guide and About sections are localized. -->
     <string name="android_asset_path">tr</string>
 
     <string name="kitkat_password_encryption_message">Android KitKat sürümünde parola şifrelemesi çalışmaz.</string>
     <string name="openkeychain_required">OpenPGP şifrelemesinin çalışması için OpenKeychain yüklü olmalıdır.</string>
     <string name="openkeychain_import_instructions">Şifresi çözüldükten sonra, şifrelenmemiş dosya ayrı bir adımda içeri aktarılmak zorundadır.</string>
+    <string name="privacy_browser_settings_pbs">Privacy Browser Ayarları.pbs</string>
     <string name="file_location">Dosya Konumu</string>
     <string name="browse">Gözat</string>
     <string name="export">Dışarı aktar</string>
     <string name="export_successful">Dışa aktarım başarılı.</string>
     <string name="export_failed">Dışa aktarım başarısız oldu:</string>
     <string name="import_failed">İçe aktarım başarısız oldu:</string>
-    <string name="cannot_export">Ayarlar, depolama alanı izni onaylanmadığından bu konuma aktarılamıyor.</string>
-    <string name="cannot_import">Ayarlar, depolama alanı izni onaylanmadığından bu konumdan aktarılamıyor.</string>
     <string name="invalid_location">geçerli bir konum değildir.</string>
     <string name="storage_permission">Depolama Alanı İzni</string>
     <string name="storage_permission_message">Privacy Browser, genel dizinlere erişmek için depolama alanı iznine ihtiyaç duymaktadır. Reddedildiği takdirde, uygulamanın dizinleri hala kullanılabilir.</string>
     <string name="storage_permission_explanation">Genel dizinlerdeki dosyalara erişim icin depolama alanı izni gerekmektedir. Aksi takdirde, sadece uygulamanın dizinleri çalışacaktır.</string>
 
     <!-- Guide. -->
-    <string name="privacy_browser_guide">Privacy Browser Rehberi</string>
     <string name="overview">Genel Bakış</string>
     <string name="local_storage">Yerel Depolama Alanı</string>
     <string name="ssl_certificates">SSL Sertifikaları</string>
index 54a16ce0ba609a9b27e3b6e2e311fc1a16d845fe..a22b10c348609923db2f1a80d7ce6f2a82c71b02 100644 (file)
         <item name="addIcon">@drawable/add_light</item>
         <item name="addBookmarkIcon">@drawable/create_bookmark_light</item>
         <item name="addFolderIcon">@drawable/create_folder_light</item>
+        <item name="copyIcon">@drawable/copy_light</item>
+        <item name="clearIcon">@drawable/clear_light</item>
         <item name="selectAllIcon">@drawable/select_all_light</item>
         <item name="editIcon">@drawable/edit_light</item>
         <item name="moveToFolderIcon">@drawable/move_to_folder_light</item>
+        <item name="saveIcon">@drawable/save_light</item>
         <item name="sortIcon">@drawable/sort_light</item>
         <item name="actionBarPopupTheme">@style/PrivacyBrowserPopupsLight</item>
         <item name="appBarTextTheme">@style/PrivacyBrowserAppBarWhiteText</item>
         <item name="addIcon">@drawable/add_dark</item>
         <item name="addBookmarkIcon">@drawable/create_bookmark_dark</item>
         <item name="addFolderIcon">@drawable/create_folder_dark</item>
+        <item name="clearIcon">@drawable/clear_dark</item>
+        <item name="copyIcon">@drawable/copy_dark</item>
         <item name="selectAllIcon">@drawable/select_all_dark</item>
         <item name="editIcon">@drawable/edit_dark</item>
         <item name="moveToFolderIcon">@drawable/move_to_folder_dark</item>
+        <item name="saveIcon">@drawable/save_dark</item>
         <item name="sortIcon">@drawable/sort_dark</item>
         <item name="appBarTextTheme">@style/PrivacyBrowserAppBarDark</item>
         <item name="tabLayoutTheme">@style/PrivacyBrowserTabLayoutDark</item>
index 62e251c2c32e4686ba3979328ee88a688cc8f1e4..c673ba65fc6e5391fd8a7afab10000265135cdd1 100644 (file)
 
     <attr name="listSelectorDrawable" format="reference" />
 
-    <attr name="userAgentIcon" format="reference" />
-    <attr name="searchIcon" format="reference" />
-    <attr name="homepageIcon" format="reference" />
-    <attr name="fontSizeIcon" format="reference" />
-    <attr name="deleteIcon" format="reference" />
-    <attr name="addIcon" format="reference" />
+    <attr name="aboutIcon" format="reference" />
     <attr name="addBookmarkIcon" format="reference" />
     <attr name="addFolderIcon" format="reference" />
-    <attr name="selectAllIcon" format="reference" />
+    <attr name="addIcon" format="reference" />
+    <attr name="clearIcon" format="reference" />
+    <attr name="copyIcon" format="reference" />
+    <attr name="deleteIcon" format="reference" />
     <attr name="editIcon" format="reference" />
+    <attr name="fontSizeIcon" format="reference" />
+    <attr name="homepageIcon" format="reference" />
     <attr name="moveToFolderIcon" format="reference" />
-    <attr name="aboutIcon" format="reference" />
+    <attr name="saveIcon" format="reference" />
+    <attr name="searchIcon" format="reference" />
+    <attr name="selectAllIcon" format="reference" />
     <attr name="sortIcon" format="reference" />
+    <attr name="userAgentIcon" format="reference" />
 </resources>
\ No newline at end of file
index 8618c220037a49b20932793e2d0fd851d08be8c0..e14b85f6dda7ce410f8e12a78139ec32a6f2a89d 100644 (file)
@@ -28,7 +28,6 @@
 
     <!-- Activities. -->
     <string name="privacy_browser">Privacy Browser</string>
-    <string name="privacy_browser_settings">Privacy Browser Settings</string>
     <!-- For translations, `android_asset_path` should be the localization abbreviation.  For example, Spanish is `es`.  This should not be translated unless the Guide and About sections are localized. -->
     <string name="android_asset_path">en</string>
 
     <string name="downloads">Downloads</string>
     <string name="settings">Settings</string>
     <string name="import_export">Import/Export</string>
+    <string name="logcat">Logcat</string>
     <string name="guide">Guide</string>
     <string name="about">About</string>
     <string name="clear_and_exit">Clear and Exit</string>
     <string name="kitkat_password_encryption_message">Password encryption does not work on Android KitKat.</string>
     <string name="openkeychain_required">OpenPGP encryption requires that OpenKeychain be installed.</string>
     <string name="openkeychain_import_instructions">The unencrypted file will have to be imported in a separate step after it is decrypted.</string>
+    <string name="privacy_browser_settings_pbs">Privacy Browser Settings.pbs</string>
     <string name="file_location">File Location</string>
     <string name="browse">Browse</string>
     <string name="export">Export</string>
     <string name="export_successful">Export successful.</string>
     <string name="export_failed">Export failed:</string>
     <string name="import_failed">Import failed:</string>
-    <string name="cannot_export">The settings cannot be exported to this location because the storage permission has not been granted.</string>
-    <string name="cannot_import">The settings cannot be imported from this location because the storage permission has not been granted.</string>
     <string name="invalid_location">is not a valid location.</string>
     <string name="storage_permission">Storage Permission</string>
     <string name="storage_permission_message">Privacy Browser needs the storage permission to access public directories. If it is denied, the app’s directories can still be used.</string>
     <string name="storage_permission_explanation">Accessing files in public directories requires the storage permission. Otherwise, only app directories will work.</string>
+    <string name="cannot_use_location">This location cannot be used because the storage permission has not been granted.</string>
+
+    <!-- Logcat. -->
+    <string name="copy">Copy</string>
+    <string name="logcat_copied">Logcat copied.</string>
+    <string name="clear">Clear</string>
+    <string name="save_logcat">Save logcat</string>
+    <string name="privacy_browser_logcat_txt">Privacy Browser Logcat.txt</string>
+    <string name="file_saved_successfully">File saved successfully.</string>
+    <string name="save_failed">Save failed:</string>
 
     <!-- Guide. -->
-    <string name="privacy_browser_guide">Privacy Browser Guide</string>
     <string name="overview">Overview</string>
     <string name="local_storage">Local Storage</string>
     <string name="ssl_certificates">SSL Certificates</string>
             <item>WebView default user agent</item>  <!-- This item must not be translated into other languages because it is referenced in code.  It is never displayed on the screen. -->
             <item>Mozilla/5.0 (Android 9; Mobile; rv:63.0) Gecko/63.0 Firefox/63.0</item>
             <item>Mozilla/5.0 (Linux; Android 9; Pixel 2 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36</item>
-            <item>Mozilla/5.0 (iPhone; CPU iPhone OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1</item>
+            <item>Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1</item>
             <item>Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0</item>
             <item>Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36</item>
             <item>Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0</item>
             <item>Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36</item>
             <item>Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134</item>
             <item>Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko</item>
-            <item>Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15</item>
+            <item>Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.2 Safari/605.1.15</item>
             <item>Custom user agent</item>  <!-- This item must not be translated into other languages because it is referenced in code.  It is never displayed on the screen. -->
         </string-array>
         <string name="custom_user_agent">Custom user agent</string>
index 73954b2cdfa136bf63870b47bab64e0f30cb6489..40c8ba4756bdedcef90a6e5374157a7bba54d84d 100644 (file)
         <item name="addIcon">@drawable/add_light</item>
         <item name="addBookmarkIcon">@drawable/create_bookmark_light</item>
         <item name="addFolderIcon">@drawable/create_folder_light</item>
+        <item name="copyIcon">@drawable/copy_light</item>
+        <item name="clearIcon">@drawable/clear_light</item>
         <item name="selectAllIcon">@drawable/select_all_light</item>
         <item name="editIcon">@drawable/edit_light</item>
         <item name="moveToFolderIcon">@drawable/move_to_folder_light</item>
+        <item name="saveIcon">@drawable/save_light</item>
         <item name="sortIcon">@drawable/sort_light</item>
         <item name="actionBarPopupTheme">@style/PrivacyBrowserPopupsLight</item>
         <item name="appBarTextTheme">@style/PrivacyBrowserAppBarWhiteText</item>
         <item name="addIcon">@drawable/add_dark</item>
         <item name="addBookmarkIcon">@drawable/create_bookmark_dark</item>
         <item name="addFolderIcon">@drawable/create_folder_dark</item>
+        <item name="copyIcon">@drawable/copy_dark</item>
+        <item name="clearIcon">@drawable/clear_dark</item>
         <item name="selectAllIcon">@drawable/select_all_dark</item>
         <item name="editIcon">@drawable/edit_dark</item>
         <item name="moveToFolderIcon">@drawable/move_to_folder_dark</item>
+        <item name="saveIcon">@drawable/save_dark</item>
         <item name="sortIcon">@drawable/sort_dark</item>
         <item name="appBarTextTheme">@style/PrivacyBrowserAppBarDark</item>
         <item name="tabLayoutTheme">@style/PrivacyBrowserTabLayoutDark</item>