<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>
<w>licensors</w>
<w>linearlayout</w>
<w>listview</w>
+ <w>logcats</w>
<w>logins</w>
<w>lossless</w>
<w>macos</w>
<?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"
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
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;
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;
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;
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;
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) {
// 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";
}
}
});
- // 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);
}
// 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.
// 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.
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.
// 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);
}
@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);
}
// 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();
}
}
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.
// 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.
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()) {
// 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.
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() {
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.
--- /dev/null
+/*
+ * 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
@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`.
// 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.
// 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.
}
};
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);
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.
// 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.
}
}
// 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.
}
}
// 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.
}
}
// 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.
}
}
// 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.
}
}
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.
};
// 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);
@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.
}
}
- // `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;
// `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.
// 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.
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);
}
});
- // 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.
}
});
- // 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.
}
});
- // `onCreateDialog` requires the return of an `AlertDialog`.
+ // Return the alert dialog.
return alertDialog;
}
+++ /dev/null
-/*
- * 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;
- }
-}
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);
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+<!-- `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
--- /dev/null
+<!-- `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
--- /dev/null
+<!-- `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
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" />
--- /dev/null
+<!-- `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
--- /dev/null
+<!-- `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
--- /dev/null
+<!-- `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
--- /dev/null
+<!-- `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
--- /dev/null
+<!-- `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
--- /dev/null
+<!-- `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
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
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
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
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
<?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
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"
<?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>.
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"
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"
<?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"
</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"
--- /dev/null
+<?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
<?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>.
--- /dev/null
+<?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
--- /dev/null
+<?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
<?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>.
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"
--- /dev/null
+<?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
--- /dev/null
+<?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
<?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
<?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>.
<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>
<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>
<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>
<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>
<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>
<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>
<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
<!-- 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>
<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>