apply plugin: 'kotlin-android'
android {
- compileSdkVersion 29
- buildToolsVersion '29.0.2'
+ compileSdkVersion 30
defaultConfig {
minSdkVersion 19
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode 53
versionName "3.6.1"
implementation 'com.google.android.material:material:1.3.0'
// Only compile AdMob ads for the free flavor.
- freeImplementation 'com.google.android.gms:play-services-ads:19.7.0'
+ freeImplementation 'com.google.android.gms:play-services-ads:19.8.0'
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright © 2015-2020 Soren Stoutner <soren@stoutner.com>.
+ Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
<!-- Required to create home screen shortcuts. -->
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
- <!-- Required to import settings from external storage. -->
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-
- <!-- Required to export settings and save files to public storage. -->
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
<!-- Support Chromebooks that don't have a touch screen. -->
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
- <!-- For API >= 23, app data is automatically backed up to Google cloud servers unless `android:allowBackup="false"` and `android:fullBackupContent="false"` is set.
- `android:requestLegacyExternalStorage="true"` makes Android 10 storage permissions work like previous versions of Android. It is a temporary workaround. <https://redmine.stoutner.com/issues/546> -->
+ <!-- For API >= 23, app data is automatically backed up to Google cloud servers unless `android:allowBackup="false"` and `android:fullBackupContent="false"` is set. -->
<application
android:label="@string/privacy_browser"
android:icon="@mipmap/privacy_browser"
android:fullBackupContent="false"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
- android:requestLegacyExternalStorage="true"
tools:ignore="UnusedAttribute" >
<!-- If `android:name="android.webkit.WebView.MetricsOptOut"` is not `true` then `WebViews` will upload metrics to Google. <https://developer.android.com/reference/android/webkit/WebView.html> -->
<data android:mimeType="text/plain" />
</intent-filter>
- <!-- Process intents for MHT archives. -->
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
-
- <category android:name="android.intent.category.BROWSABLE" />
- <category android:name="android.intent.category.DEFAULT" />
-
- <data android:scheme="file" />
- <data android:scheme="content" />
-
- <data android:host="*" />
-
- <!-- In the path pattern syntax, `.*` is a wildcard. Hence, this matches any file path that ends in `.mht`. <https://developer.android.com/guide/topics/manifest/data-element#path> -->
- <data android:pathPattern=".*.mht" />
- <data android:mimeType="*/*" />
- </intent-filter>
-
<!-- Process web search intents. -->
<intent-filter>
<action android:name="android.intent.action.WEB_SEARCH" />
package com.stoutner.privacybrowser.activities;
-import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
-import android.content.ContentResolver;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.media.MediaScannerConnection;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
-import android.widget.TextView;
-import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.FileProvider;
import androidx.fragment.app.DialogFragment;
import androidx.viewpager.widget.ViewPager;
import com.stoutner.privacybrowser.R;
import com.stoutner.privacybrowser.asynctasks.SaveAboutVersionImage;
import com.stoutner.privacybrowser.dialogs.SaveDialog;
-import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
import com.stoutner.privacybrowser.fragments.AboutVersionFragment;
-import com.stoutner.privacybrowser.helpers.FileNameHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
-public class AboutActivity extends AppCompatActivity implements SaveDialog.SaveListener, StoragePermissionDialog.StoragePermissionDialogListener {
+public class AboutActivity extends AppCompatActivity implements SaveDialog.SaveListener {
// Declare the class variables.
- private String filePathString;
private AboutPagerAdapter aboutPagerAdapter;
- // Declare the class views.
- private LinearLayout aboutVersionLinearLayout;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
// Get a handle for the shared preferences.
aboutTabLayout.setupWithViewPager(aboutViewPager);
}
- @Override
- public void onSave(int saveType, DialogFragment dialogFragment) {
- // Get a handle for the dialog.
- Dialog dialog = dialogFragment.getDialog();
-
- // Remove the lint warning below that the dialog might be null.
- assert dialog != null;
-
- // Get a handle for the file name edit text.
- EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
-
- // Get the file path string.
- filePathString = fileNameEditText.getText().toString();
-
- // Get a handle for the about version linear layout.
- aboutVersionLinearLayout = findViewById(R.id.about_version_linearlayout);
-
- // 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 file according to the type.
- switch (saveType) {
- case SaveDialog.SAVE_ABOUT_VERSION_TEXT:
- // Save the about version text.
- saveAsText(filePathString);
- break;
-
- case SaveDialog.SAVE_ABOUT_VERSION_IMAGE:
- // Save the about version image.
- new SaveAboutVersionImage(this, this, filePathString, aboutVersionLinearLayout).execute();
- break;
- }
-
- // Reset the file path string.
- 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 webpage according to the type.
- switch (saveType) {
- case SaveDialog.SAVE_ABOUT_VERSION_TEXT:
- // Save the about version text.
- saveAsText(filePathString);
- break;
-
- case SaveDialog.SAVE_ABOUT_VERSION_IMAGE:
- // Save the about version image.
- new SaveAboutVersionImage(this, this, filePathString, aboutVersionLinearLayout).execute();
- break;
- }
-
- // Reset the file path string.
- filePathString = "";
- } else { // The file path is 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.
- // Declare a storage permission dialog fragment.
- DialogFragment storagePermissionDialogFragment;
-
- // Instantiate the storage permission alert dialog according to the type.
- if (saveType == SaveDialog.SAVE_ABOUT_VERSION_TEXT) {
- storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.SAVE_TEXT);
- } else {
- storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.SAVE_IMAGE);
- }
-
- // 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.
- switch (saveType) {
- case SaveDialog.SAVE_ABOUT_VERSION_TEXT:
- // Request the write external storage permission. The text will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, StoragePermissionDialog.SAVE_TEXT);
- break;
-
- case SaveDialog.SAVE_ABOUT_VERSION_IMAGE:
- // Request the write external storage permission. The image will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, StoragePermissionDialog.SAVE_IMAGE);
- break;
- }
-
- }
- }
- }
- }
-
- @Override
- public void onCloseStoragePermissionDialog(int requestType) {
- // Request the write external storage permission according to the request type. About version will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestType);
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included).
- if (grantResults.length > 0) {
- // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
- switch (requestCode) {
- case StoragePermissionDialog.SAVE_TEXT:
- // Save the about version text.
- saveAsText(filePathString);
- break;
-
- case StoragePermissionDialog.SAVE_IMAGE:
- // Save the about version image.
- new SaveAboutVersionImage(this, this, filePathString, aboutVersionLinearLayout).execute();
- break;
- }
- } else{ // the storage permission was not granted.
- // Display an error snackbar.
- Snackbar.make(aboutVersionLinearLayout, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
- }
-
- // Reset the file path string.
- filePathString = "";
- }
- }
-
// 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) {
+ public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
// Run the default commands.
- super.onActivityResult(requestCode, resultCode, data);
+ super.onActivityResult(requestCode, resultCode, returnedIntent);
// Only do something if the user didn't press back from the file picker.
if (resultCode == Activity.RESULT_OK) {
// Get a handle for the dialog view.
EditText fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext);
- TextView fileExistsWarningTextView = saveDialog.findViewById(R.id.file_exists_warning_textview);
// Get the file name URI from the intent.
- Uri fileNameUri = data.getData();
+ Uri fileNameUri = returnedIntent.getData();
- // Process the file name URI if it is not null.
- if (fileNameUri != null) {
- // Instantiate a file name helper.
- FileNameHelper fileNameHelper = new FileNameHelper();
+ // Get the file name string from the URI.
+ String fileNameString = fileNameUri.toString();
- // Convert the file name URI to a file name path.
- String fileNamePath = fileNameHelper.convertUriToFileNamePath(fileNameUri);
+ // Set the file name text.
+ fileNameEditText.setText(fileNameString);
- // Set the file name path as the text of the file nam edit text.
- fileNameEditText.setText(fileNamePath);
-
- // Move the cursor to the end of the file name edit text.
- fileNameEditText.setSelection(fileNamePath.length());
-
- // Hid ethe file exists warning.
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNameString.length());
}
}
}
- private void saveAsText(String fileNameString) {
- try {
- // Get a handle for the about about version fragment.
- AboutVersionFragment aboutVersionFragment = (AboutVersionFragment) aboutPagerAdapter.getTabFragment(0);
-
- // Get the about version text.
- String aboutVersionString = aboutVersionFragment.getAboutVersionString();
-
- // Create an input stream with the contents of about version.
- InputStream aboutVersionInputStream = new ByteArrayInputStream(aboutVersionString.getBytes(StandardCharsets.UTF_8));
-
- // Create an about version buffered reader.
- BufferedReader aboutVersionBufferedReader = new BufferedReader(new InputStreamReader(aboutVersionInputStream));
-
- // Create a file from the file name string.
- File saveFile = new File(fileNameString);
+ @Override
+ public void onSave(int saveType, DialogFragment dialogFragment) {
+ // Get a handle for the dialog.
+ Dialog dialog = dialogFragment.getDialog();
- // Delete the file if it already exists.
- if (saveFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- saveFile.delete();
- }
+ // Remove the lint warning below that the dialog might be null.
+ assert dialog != null;
- // Create a file buffered writer.
- BufferedWriter fileBufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(saveFile)));
+ // Get a handle for the file name edit text.
+ EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
- // Create a transfer string.
- String transferString;
+ // Get the file name string.
+ String fileNameString = fileNameEditText.getText().toString();
- // Use the transfer string to copy the about version text from the buffered reader to the buffered writer.
- while ((transferString = aboutVersionBufferedReader.readLine()) != null) {
- // Append the line to the buffered writer.
- fileBufferedWriter.append(transferString);
+ // Get a handle for the about version linear layout.
+ LinearLayout aboutVersionLinearLayout = findViewById(R.id.about_version_linearlayout);
- // Append a line break.
- fileBufferedWriter.append("\n");
- }
+ // Save the file according to the type.
+ switch (saveType) {
+ case SaveDialog.SAVE_ABOUT_VERSION_TEXT:
+ try {
+ // Get a handle for the about version fragment.
+ AboutVersionFragment aboutVersionFragment = (AboutVersionFragment) aboutPagerAdapter.getTabFragment(0);
- // Close the buffered reader and writer.
- aboutVersionBufferedReader.close();
- fileBufferedWriter.close();
+ // Get the about version text.
+ String aboutVersionString = aboutVersionFragment.getAboutVersionString();
- // 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);
+ // Create an input stream with the contents of about version.
+ InputStream aboutVersionInputStream = new ByteArrayInputStream(aboutVersionString.getBytes(StandardCharsets.UTF_8));
- // Create an about version saved snackbar.
- Snackbar aboutVersionSavedSnackbar = Snackbar.make(aboutVersionLinearLayout, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT);
+ // Create an about version buffered reader.
+ BufferedReader aboutVersionBufferedReader = new BufferedReader(new InputStreamReader(aboutVersionInputStream));
- // Add an open option to the snackbar.
- aboutVersionSavedSnackbar.setAction(R.string.open, (View view) -> {
- // Get a file for the file name string.
- File file = new File(fileNameString);
+ // Open an output stream.
+ OutputStream outputStream = getContentResolver().openOutputStream(Uri.parse(fileNameString));
- // Declare a file URI variable.
- Uri fileUri;
+ // Create a file buffered writer.
+ BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
- // Get the URI for the file according to the Android version.
- if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
- fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file);
- } else { // Get the raw file path URI.
- fileUri = Uri.fromFile(file);
- }
+ // Create a transfer string.
+ String transferString;
- // Get a handle for the content resolver.
- ContentResolver contentResolver = getContentResolver();
+ // Use the transfer string to copy the about version text from the buffered reader to the buffered writer.
+ while ((transferString = aboutVersionBufferedReader.readLine()) != null) {
+ // Append the line to the buffered writer.
+ bufferedWriter.append(transferString);
- // Create an open intent with `ACTION_VIEW`.
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
+ // Append a line break.
+ bufferedWriter.append("\n");
+ }
- // Set the URI and the MIME type.
- openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
+ // Flush the buffered writer.
+ bufferedWriter.flush();
- // Allow the app to read the file URI.
- openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ // Close the inputs and outputs.
+ aboutVersionBufferedReader.close();
+ aboutVersionInputStream.close();
+ bufferedWriter.close();
+ outputStream.close();
- // Show the chooser.
- startActivity(Intent.createChooser(openIntent, getString(R.string.open)));
- });
+ // Display a snackbar with the saved about version information.
+ Snackbar.make(aboutVersionLinearLayout, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show();
+ } catch (Exception exception) {
+ // Display a snackbar with the error message.
+ Snackbar.make(aboutVersionLinearLayout, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ }
+ break;
- // Show the about version saved snackbar.
- aboutVersionSavedSnackbar.show();
- } catch (Exception exception) {
- // Display a snackbar with the error message.
- Snackbar.make(aboutVersionLinearLayout, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ case SaveDialog.SAVE_ABOUT_VERSION_IMAGE:
+ // Save the about version image.
+ new SaveAboutVersionImage(this, fileNameString, aboutVersionLinearLayout).execute();
+ break;
}
}
}
\ No newline at end of file
/*
- * Copyright © 2018-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2018-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.activities;
-import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
import android.os.Handler;
import android.preference.PreferenceManager;
-import android.provider.DocumentsContract;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.cardview.widget.CardView;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
-import androidx.fragment.app.DialogFragment;
+import androidx.multidex.BuildConfig;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
-import com.stoutner.privacybrowser.BuildConfig;
import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
-import com.stoutner.privacybrowser.helpers.DownloadLocationHelper;
-import com.stoutner.privacybrowser.helpers.FileNameHelper;
import com.stoutner.privacybrowser.helpers.ImportExportDatabaseHelper;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
-public class ImportExportActivity extends AppCompatActivity implements StoragePermissionDialog.StoragePermissionDialogListener {
+public class ImportExportActivity extends AppCompatActivity {
// Define the encryption constants.
private final int NO_ENCRYPTION = 0;
private final int PASSWORD_ENCRYPTION = 1;
// Define the activity result constants.
private final int BROWSE_RESULT_CODE = 0;
- private final int OPENPGP_EXPORT_RESULT_CODE = 1;
+ private final int OPENPGP_IMPORT_RESULT_CODE = 1;
+ private final int OPENPGP_EXPORT_RESULT_CODE = 2;
// Define the saved instance state constants.
- private final String PASSWORD_ENCRYPTED_TEXTINPUTLAYOUT_VISIBILITY = "password_encrypted_textinputlayout_visibility";
+ private final String ENCRYPTION_PASSWORD_TEXTINPUTLAYOUT_VISIBILITY = "encryption_password_textinputlayout_visibility";
private final String KITKAT_PASSWORD_ENCRYPTED_TEXTVIEW_VISIBILITY = "kitkat_password_encrypted_textview_visibility";
private final String OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY = "open_keychain_required_textview_visibility";
private final String FILE_LOCATION_CARD_VIEW = "file_location_card_view";
private final String FILE_NAME_LINEARLAYOUT_VISIBILITY = "file_name_linearlayout_visibility";
- private final String FILE_DOES_NOT_EXIST_TEXTVIEW_VISIBILITY = "file_does_not_exist_textview_visibility";
- private final String FILE_EXISTS_WARNING_TEXTVIEW_VISIBILITY = "file_exists_warning_textview_visibility";
private final String OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY = "open_keychain_import_instructions_textview_visibility";
private final String IMPORT_EXPORT_BUTTON_VISIBILITY = "import_export_button_visibility";
private final String FILE_NAME_TEXT = "file_name_text";
private final String IMPORT_EXPORT_BUTTON_TEXT = "import_export_button_text";
// Define the class views.
- TextInputLayout passwordEncryptionTextInputLayout;
+ Spinner encryptionSpinner;
+ TextInputLayout encryptionPasswordTextInputLayout;
+ EditText encryptionPasswordEditText;
TextView kitKatPasswordEncryptionTextView;
TextView openKeychainRequiredTextView;
CardView fileLocationCardView;
+ RadioButton importRadioButton;
LinearLayout fileNameLinearLayout;
EditText fileNameEditText;
- TextView fileDoesNotExistTextView;
- TextView fileExistsWarningTextView;
TextView openKeychainImportInstructionsTextView;
Button importExportButton;
// Define the class variables.
private boolean openKeychainInstalled;
+ private File temporaryPgpEncryptedImportFile;
+ private File temporaryPreEncryptedExportFile;
@Override
public void onCreate(Bundle savedInstanceState) {
}
// Get handles for the views that need to be modified.
- Spinner encryptionSpinner = findViewById(R.id.encryption_spinner);
- passwordEncryptionTextInputLayout = findViewById(R.id.password_encryption_textinputlayout);
- EditText encryptionPasswordEditText = findViewById(R.id.password_encryption_edittext);
+ encryptionSpinner = findViewById(R.id.encryption_spinner);
+ encryptionPasswordTextInputLayout = findViewById(R.id.encryption_password_textinputlayout);
+ encryptionPasswordEditText = findViewById(R.id.encryption_password_edittext);
kitKatPasswordEncryptionTextView = findViewById(R.id.kitkat_password_encryption_textview);
openKeychainRequiredTextView = findViewById(R.id.openkeychain_required_textview);
fileLocationCardView = findViewById(R.id.file_location_cardview);
- RadioButton importRadioButton = findViewById(R.id.import_radiobutton);
+ importRadioButton = findViewById(R.id.import_radiobutton);
RadioButton exportRadioButton = findViewById(R.id.export_radiobutton);
fileNameLinearLayout = findViewById(R.id.file_name_linearlayout);
fileNameEditText = findViewById(R.id.file_name_edittext);
- fileDoesNotExistTextView = findViewById(R.id.file_does_not_exist_textview);
- fileExistsWarningTextView = findViewById(R.id.file_exists_warning_textview);
openKeychainImportInstructionsTextView = findViewById(R.id.openkeychain_import_instructions_textview);
importExportButton = findViewById(R.id.import_export_button);
- TextView storagePermissionTextView = findViewById(R.id.import_export_storage_permission_textview);
// Create an array adapter for the spinner.
ArrayAdapter<CharSequence> encryptionArrayAdapter = ArrayAdapter.createFromResource(this, R.array.encryption_type, R.layout.spinner_item);
// Set the array adapter for the spinner.
encryptionSpinner.setAdapter(encryptionArrayAdapter);
- // Instantiate the download location helper.
- DownloadLocationHelper downloadLocationHelper = new DownloadLocationHelper();
-
- // Get the default file path.
- String defaultFilePath = downloadLocationHelper.getDownloadLocation(this) + "/" + getString(R.string.settings) + " " + BuildConfig.VERSION_NAME + ".pbs";
-
- // Set the other default file paths.
- String defaultPasswordEncryptionFilePath = defaultFilePath + ".aes";
- String defaultPgpFilePath = defaultFilePath + ".pgp";
-
- // Set the default file path.
- fileNameEditText.setText(defaultFilePath);
-
- // Hide the storage permission text view if the permission has already been granted.
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
- storagePermissionTextView.setVisibility(View.GONE);
- }
-
// Update the UI when the spinner changes.
encryptionSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
switch (position) {
case NO_ENCRYPTION:
// Hide the unneeded layout items.
- passwordEncryptionTextInputLayout.setVisibility(View.GONE);
+ encryptionPasswordTextInputLayout.setVisibility(View.GONE);
kitKatPasswordEncryptionTextView.setVisibility(View.GONE);
openKeychainRequiredTextView.setVisibility(View.GONE);
openKeychainImportInstructionsTextView.setVisibility(View.GONE);
importExportButton.setText(R.string.import_button);
}
- // Reset the default file path.
- fileNameEditText.setText(defaultFilePath);
+ // Enable the import/export button if the file name is populated.
+ importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty());
break;
case PASSWORD_ENCRYPTION:
openKeychainImportInstructionsTextView.setVisibility(View.GONE);
// Show the password encryption layout items.
- passwordEncryptionTextInputLayout.setVisibility(View.VISIBLE);
+ encryptionPasswordTextInputLayout.setVisibility(View.VISIBLE);
// Show the file location card.
fileLocationCardView.setVisibility(View.VISIBLE);
importExportButton.setText(R.string.import_button);
}
- // Update the default file path.
- fileNameEditText.setText(defaultPasswordEncryptionFilePath);
+ // Enable the import/button if both the password and the file name are populated.
+ importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty());
}
break;
case OPENPGP_ENCRYPTION:
// Hide the password encryption layout items.
- passwordEncryptionTextInputLayout.setVisibility(View.GONE);
+ encryptionPasswordTextInputLayout.setVisibility(View.GONE);
kitKatPasswordEncryptionTextView.setVisibility(View.GONE);
// Updated items based on the installation status of OpenKeychain.
if (openKeychainInstalled) { // OpenKeychain is installed.
- // Update the default file path.
- fileNameEditText.setText(defaultPgpFilePath);
-
// Show the file location card.
fileLocationCardView.setVisibility(View.VISIBLE);
// Set the text of the import button to be `Decrypt`.
importExportButton.setText(R.string.decrypt);
+
+ // Enable the import button if the file name is populated.
+ importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty());
} else if (exportRadioButton.isChecked()) {
// Hide the file name linear layout and the OpenKeychain import instructions.
fileNameLinearLayout.setVisibility(View.GONE);
openKeychainImportInstructionsTextView.setVisibility(View.GONE);
+
+ // Enable the export button.
+ importExportButton.setEnabled(true);
}
} else { // OpenKeychain is not installed.
// Show the OpenPGP required layout item.
@Override
public void afterTextChanged(Editable s) {
- // Get the current file name.
- String fileNameString = fileNameEditText.getText().toString();
-
- // Convert the file name string to a file.
- File file = new File(fileNameString);
-
- // Update the import/export button.
- if (importRadioButton.isChecked()) { // The import radio button is checked.
- // Enable the import button if the file and the password exists.
- importExportButton.setEnabled(file.exists() && !encryptionPasswordEditText.getText().toString().isEmpty());
- } else if (exportRadioButton.isChecked()) { // The export radio button is checked.
- // Enable the export button if the file string and the password exists.
- importExportButton.setEnabled(!fileNameString.isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty());
- }
+ // Enable the import/export button if both the file string and the password are populated.
+ importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty());
}
});
@Override
public void afterTextChanged(Editable s) {
- // Get the current file name.
- String fileNameString = fileNameEditText.getText().toString();
-
- // Convert the file name string to a file.
- File file = new File(fileNameString);
-
// Adjust the UI according to the encryption spinner position.
- switch (encryptionSpinner.getSelectedItemPosition()) {
- case NO_ENCRYPTION:
- // Determine if import or export is checked.
- if (exportRadioButton.isChecked()) { // The export radio button is checked.
- // Hide the file does not exist text view.
- fileDoesNotExistTextView.setVisibility(View.GONE);
-
- // Display a warning if the file already exists.
- if (file.exists()) {
- fileExistsWarningTextView.setVisibility(View.VISIBLE);
- } else {
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
-
- // Enable the export button if the file name is populated.
- importExportButton.setEnabled(!fileNameString.isEmpty());
- } else if (importRadioButton.isChecked()) { // The import radio button is checked.
- // Hide the file exists warning text view.
- fileExistsWarningTextView.setVisibility(View.GONE);
-
- // Check if the file exists.
- if (file.exists()) { // The file exists.
- // Hide the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.GONE);
-
- // Enable the import button.
- importExportButton.setEnabled(true);
- } else { // The file does not exist.
- // Show a notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.VISIBLE);
-
- // Disable the import button.
- importExportButton.setEnabled(false);
- }
- } else { // Neither radio button is checked.
- // Hide the file notification text views.
- fileExistsWarningTextView.setVisibility(View.GONE);
- fileDoesNotExistTextView.setVisibility(View.GONE);
- }
- break;
-
- case PASSWORD_ENCRYPTION:
- // Determine if import or export is checked.
- if (exportRadioButton.isChecked()) { // The export radio button is checked.
- // Hide the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.GONE);
-
- // Display a warning if the file already exists.
- if (file.exists()) {
- fileExistsWarningTextView.setVisibility(View.VISIBLE);
- } else {
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
-
- // Enable the export button if the file name and the password are populated.
- importExportButton.setEnabled(!fileNameString.isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty());
- } else if (importRadioButton.isChecked()) { // The import radio button is checked.
- // Hide the file exists warning text view.
- fileExistsWarningTextView.setVisibility(View.GONE);
-
- // Check if the file exists.
- if (file.exists()) { // The file exists.
- // Hide the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.GONE);
-
- // Enable the import button if the password is populated.
- importExportButton.setEnabled(!encryptionPasswordEditText.getText().toString().isEmpty());
- } else { // The file does not exist.
- // Show a notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.VISIBLE);
-
- // Disable the import button.
- importExportButton.setEnabled(false);
- }
- } else { // Neither radio button is checked.
- // Hide the file notification text views.
- fileExistsWarningTextView.setVisibility(View.GONE);
- fileDoesNotExistTextView.setVisibility(View.GONE);
- }
- break;
-
- case OPENPGP_ENCRYPTION:
- // Hide the file exists warning text view.
- fileExistsWarningTextView.setVisibility(View.GONE);
-
- if (importRadioButton.isChecked()) { // The import radio button is checked.
- if (file.exists()) { // The file exists.
- // Hide the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.GONE);
-
- // Enable the import button if OpenKeychain is installed.
- importExportButton.setEnabled(openKeychainInstalled);
- } else { // The file does not exist.
- // Show the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.VISIBLE);
-
- // Disable the import button.
- importExportButton.setEnabled(false);
- }
- } else if (exportRadioButton.isChecked()){ // The export radio button is checked.
- // Hide the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.GONE);
-
- // Enable the export button.
- importExportButton.setEnabled(true);
- } else { // Neither radio button is checked.
- // Hide the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.GONE);
- }
- break;
+ if (encryptionSpinner.getSelectedItemPosition() == PASSWORD_ENCRYPTION) {
+ // Enable the import/export button if both the file name and the password are populated.
+ importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty());
+ } else {
+ // Enable the export button if the file name is populated.
+ importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty());
}
}
});
// Check to see if the activity has been restarted.
if (savedInstanceState == null) { // The app has not been restarted.
// Initially hide the unneeded views.
- passwordEncryptionTextInputLayout.setVisibility(View.GONE);
+ encryptionPasswordTextInputLayout.setVisibility(View.GONE);
kitKatPasswordEncryptionTextView.setVisibility(View.GONE);
openKeychainRequiredTextView.setVisibility(View.GONE);
fileNameLinearLayout.setVisibility(View.GONE);
- fileDoesNotExistTextView.setVisibility(View.GONE);
- fileExistsWarningTextView.setVisibility(View.GONE);
openKeychainImportInstructionsTextView.setVisibility(View.GONE);
importExportButton.setVisibility(View.GONE);
} else { // The app has been restarted.
// Restore the visibility of the views.
- passwordEncryptionTextInputLayout.setVisibility(savedInstanceState.getInt(PASSWORD_ENCRYPTED_TEXTINPUTLAYOUT_VISIBILITY));
+ encryptionPasswordTextInputLayout.setVisibility(savedInstanceState.getInt(ENCRYPTION_PASSWORD_TEXTINPUTLAYOUT_VISIBILITY));
kitKatPasswordEncryptionTextView.setVisibility(savedInstanceState.getInt(KITKAT_PASSWORD_ENCRYPTED_TEXTVIEW_VISIBILITY));
openKeychainRequiredTextView.setVisibility(savedInstanceState.getInt(OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY));
fileLocationCardView.setVisibility(savedInstanceState.getInt(FILE_LOCATION_CARD_VIEW));
fileNameLinearLayout.setVisibility(savedInstanceState.getInt(FILE_NAME_LINEARLAYOUT_VISIBILITY));
- fileDoesNotExistTextView.setVisibility(savedInstanceState.getInt(FILE_DOES_NOT_EXIST_TEXTVIEW_VISIBILITY));
- fileExistsWarningTextView.setVisibility(savedInstanceState.getInt(FILE_EXISTS_WARNING_TEXTVIEW_VISIBILITY));
openKeychainImportInstructionsTextView.setVisibility(savedInstanceState.getInt(OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY));
importExportButton.setVisibility(savedInstanceState.getInt(IMPORT_EXPORT_BUTTON_VISIBILITY));
super.onSaveInstanceState(savedInstanceState);
// Save the visibility of the views.
- savedInstanceState.putInt(PASSWORD_ENCRYPTED_TEXTINPUTLAYOUT_VISIBILITY, passwordEncryptionTextInputLayout.getVisibility());
+ savedInstanceState.putInt(ENCRYPTION_PASSWORD_TEXTINPUTLAYOUT_VISIBILITY, encryptionPasswordTextInputLayout.getVisibility());
savedInstanceState.putInt(KITKAT_PASSWORD_ENCRYPTED_TEXTVIEW_VISIBILITY, kitKatPasswordEncryptionTextView.getVisibility());
savedInstanceState.putInt(OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY, openKeychainRequiredTextView.getVisibility());
savedInstanceState.putInt(FILE_LOCATION_CARD_VIEW, fileLocationCardView.getVisibility());
savedInstanceState.putInt(FILE_NAME_LINEARLAYOUT_VISIBILITY, fileNameLinearLayout.getVisibility());
- savedInstanceState.putInt(FILE_DOES_NOT_EXIST_TEXTVIEW_VISIBILITY, fileDoesNotExistTextView.getVisibility());
- savedInstanceState.putInt(FILE_EXISTS_WARNING_TEXTVIEW_VISIBILITY, fileExistsWarningTextView.getVisibility());
savedInstanceState.putInt(OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY, openKeychainImportInstructionsTextView.getVisibility());
savedInstanceState.putInt(IMPORT_EXPORT_BUTTON_VISIBILITY, importExportButton.getVisibility());
}
public void onClickRadioButton(View view) {
- // Get handles for the views.
- Spinner encryptionSpinner = findViewById(R.id.encryption_spinner);
- LinearLayout fileNameLinearLayout = findViewById(R.id.file_name_linearlayout);
- EditText passwordEncryptionEditText = findViewById(R.id.password_encryption_edittext);
- EditText fileNameEditText = findViewById(R.id.file_name_edittext);
- TextView fileDoesNotExistTextView = findViewById(R.id.file_does_not_exist_textview);
- TextView fileExistsWarningTextView = findViewById(R.id.file_exists_warning_textview);
- TextView openKeychainImportInstructionTextView = findViewById(R.id.openkeychain_import_instructions_textview);
- Button importExportButton = findViewById(R.id.import_export_button);
-
// Get the current file name.
String fileNameString = fileNameEditText.getText().toString();
File file = new File(fileNameString);
// Check to see if import or export was selected.
- switch (view.getId()) {
- case R.id.import_radiobutton:
- // Check to see if OpenPGP encryption is selected.
- if (encryptionSpinner.getSelectedItemPosition() == OPENPGP_ENCRYPTION) { // OpenPGP encryption selected.
- // Show the OpenKeychain import instructions.
- openKeychainImportInstructionTextView.setVisibility(View.VISIBLE);
-
- // Set the text on the import/export button to be `Decrypt`.
- importExportButton.setText(R.string.decrypt);
- } else { // OpenPGP encryption not selected.
- // Hide the OpenKeychain import instructions.
- openKeychainImportInstructionTextView.setVisibility(View.GONE);
-
- // Set the text on the import/export button to be `Import`.
- importExportButton.setText(R.string.import_button);
- }
-
- // Hide the file exists warning text view.
- fileExistsWarningTextView.setVisibility(View.GONE);
+ if (view.getId() == R.id.import_radiobutton) { // The import radio button is selected.
+ // Check to see if OpenPGP encryption is selected.
+ if (encryptionSpinner.getSelectedItemPosition() == OPENPGP_ENCRYPTION) { // OpenPGP encryption selected.
+ // Show the OpenKeychain import instructions.
+ openKeychainImportInstructionsTextView.setVisibility(View.VISIBLE);
+
+ // Set the text on the import/export button to be `Decrypt`.
+ importExportButton.setText(R.string.decrypt);
+ } else { // OpenPGP encryption not selected.
+ // Hide the OpenKeychain import instructions.
+ openKeychainImportInstructionsTextView.setVisibility(View.GONE);
- // Display the file name views.
- fileNameLinearLayout.setVisibility(View.VISIBLE);
- importExportButton.setVisibility(View.VISIBLE);
-
- // Check to see if the file exists.
- if (file.exists()) { // The file exists.
- // Hide the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.GONE);
-
- // Check to see if password encryption is selected.
- if (encryptionSpinner.getSelectedItemPosition() == PASSWORD_ENCRYPTION) { // Password encryption is selected.
- // Enable the import button if the encryption password is populated.
- importExportButton.setEnabled(!passwordEncryptionEditText.getText().toString().isEmpty());
- } else { // Password encryption is not selected.
- // Enable the import/decrypt button.
- importExportButton.setEnabled(true);
- }
- } else { // The file does not exist.
- // Show the notification that the file does not exist.
- fileDoesNotExistTextView.setVisibility(View.VISIBLE);
+ // Set the text on the import/export button to be `Import`.
+ importExportButton.setText(R.string.import_button);
+ }
- // Disable the import/decrypt button.
- importExportButton.setEnabled(false);
+ // Display the file name views.
+ fileNameLinearLayout.setVisibility(View.VISIBLE);
+ importExportButton.setVisibility(View.VISIBLE);
+
+ // Check to see if the file exists.
+ if (file.exists()) { // The file exists.
+ // Check to see if password encryption is selected.
+ if (encryptionSpinner.getSelectedItemPosition() == PASSWORD_ENCRYPTION) { // Password encryption is selected.
+ // Enable the import button if the encryption password is populated.
+ importExportButton.setEnabled(!encryptionPasswordEditText.getText().toString().isEmpty());
+ } else { // Password encryption is not selected.
+ // Enable the import/decrypt button.
+ importExportButton.setEnabled(true);
}
- break;
-
- case R.id.export_radiobutton:
- // Hide the OpenKeychain import instructions.
- openKeychainImportInstructionTextView.setVisibility(View.GONE);
+ } else { // The file does not exist.
+ // Disable the import/decrypt button.
+ importExportButton.setEnabled(false);
+ }
+ } else { // The export radio button is selected.
+ // Hide the OpenKeychain import instructions.
+ openKeychainImportInstructionsTextView.setVisibility(View.GONE);
- // Set the text on the import/export button to be `Export`.
- importExportButton.setText(R.string.export);
+ // Set the text on the import/export button to be `Export`.
+ importExportButton.setText(R.string.export);
- // Show the import/export button.
- importExportButton.setVisibility(View.VISIBLE);
+ // Show the import/export button.
+ importExportButton.setVisibility(View.VISIBLE);
- // Check to see if OpenPGP encryption is selected.
- if (encryptionSpinner.getSelectedItemPosition() == OPENPGP_ENCRYPTION) { // OpenPGP encryption is selected.
- // Hide the file name views.
- fileNameLinearLayout.setVisibility(View.GONE);
- fileDoesNotExistTextView.setVisibility(View.GONE);
- fileExistsWarningTextView.setVisibility(View.GONE);
+ // Check to see if OpenPGP encryption is selected.
+ if (encryptionSpinner.getSelectedItemPosition() == OPENPGP_ENCRYPTION) { // OpenPGP encryption is selected.
+ // Hide the file name views.
+ fileNameLinearLayout.setVisibility(View.GONE);
- // Enable the export button.
- importExportButton.setEnabled(true);
- } else { // OpenPGP encryption is not selected.
- // Show the file name view.
- fileNameLinearLayout.setVisibility(View.VISIBLE);
-
- // Hide the notification that the file name does not exist.
- fileDoesNotExistTextView.setVisibility(View.GONE);
-
- // Display a warning if the file already exists.
- if (file.exists()) {
- fileExistsWarningTextView.setVisibility(View.VISIBLE);
- } else {
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
+ // Enable the export button.
+ importExportButton.setEnabled(true);
+ } else { // OpenPGP encryption is not selected.
+ // Show the file name view.
+ fileNameLinearLayout.setVisibility(View.VISIBLE);
- // Check the encryption type.
- if (encryptionSpinner.getSelectedItemPosition() == NO_ENCRYPTION) { // No encryption is selected.
- // Enable the export button if the file name is populated.
- importExportButton.setEnabled(!fileNameString.isEmpty());
- } else { // Password encryption is selected.
- // Enable the export button if the file name and the password are populated.
- importExportButton.setEnabled(!fileNameString.isEmpty() && !passwordEncryptionEditText.getText().toString().isEmpty());
- }
+ // Check the encryption type.
+ if (encryptionSpinner.getSelectedItemPosition() == NO_ENCRYPTION) { // No encryption is selected.
+ // Enable the export button if the file name is populated.
+ importExportButton.setEnabled(!fileNameString.isEmpty());
+ } else { // Password encryption is selected.
+ // Enable the export button if the file name and the password are populated.
+ importExportButton.setEnabled(!fileNameString.isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty());
}
- break;
+ }
}
}
public void browse(View view) {
- // Get a handle for the views.
- Spinner encryptionSpinner = findViewById(R.id.encryption_spinner);
- RadioButton importRadioButton = findViewById(R.id.import_radiobutton);
-
// Check to see if import or export is selected.
if (importRadioButton.isChecked()) { // Import is selected.
// Create the file picker intent.
// Set the intent MIME type to include all files so that everything is visible.
importBrowseIntent.setType("*/*");
- // Set the initial directory if the minimum API >= 26.
- if (Build.VERSION.SDK_INT >= 26) {
- importBrowseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
- }
-
// Request a file that can be opened.
importBrowseIntent.addCategory(Intent.CATEGORY_OPENABLE);
exportBrowseIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.settings) + " " + BuildConfig.VERSION_NAME + ".pbs.aes");
}
- // Set the initial directory if the minimum API >= 26.
- if (Build.VERSION.SDK_INT >= 26) {
- exportBrowseIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory());
- }
-
// Request a file that can be opened.
exportBrowseIntent.addCategory(Intent.CATEGORY_OPENABLE);
}
}
- public void importExport(View view) {
- // Get a handle for the views.
- Spinner encryptionSpinner = findViewById(R.id.encryption_spinner);
- RadioButton importRadioButton = findViewById(R.id.import_radiobutton);
- RadioButton exportRadioButton = findViewById(R.id.export_radiobutton);
-
- // Check to see if the storage permission is needed.
- if ((encryptionSpinner.getSelectedItemPosition() == OPENPGP_ENCRYPTION) && exportRadioButton.isChecked()) { // Permission not needed to export via OpenKeychain.
- // Export the settings.
- exportSettings();
- } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
- // Check to see if import or export is selected.
- if (importRadioButton.isChecked()) { // Import is selected.
- // Import the settings.
- importSettings();
- } else { // Export is selected.
- // Export the settings.
- exportSettings();
- }
- } else { // The storage permission has not been granted.
- // Get a handle for the file name EditText.
- EditText fileNameEditText = findViewById(R.id.file_name_edittext);
-
- // Get the file name string.
- String fileNameString = fileNameEditText.getText().toString();
-
- // 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 (fileNameString.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory.
- // Check to see if import or export is selected.
- if (importRadioButton.isChecked()) { // Import is selected.
- // Import the settings.
- importSettings();
- } else { // Export is selected.
- // Export the settings.
- exportSettings();
- }
- } else { // The file path is 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 = StoragePermissionDialog.displayDialog(0);
-
- // 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 export will be run when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
- }
- }
- }
- }
-
- @Override
- public void onCloseStoragePermissionDialog(int type) {
- // 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);
- }
-
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- // Get a handle for the import radiobutton.
- RadioButton importRadioButton = findViewById(R.id.import_radiobutton);
-
- // 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 { // 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_use_location), Snackbar.LENGTH_LONG).show();
- }
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
// Run the default commands.
- super.onActivityResult(requestCode, resultCode, intent);
+ super.onActivityResult(requestCode, resultCode, returnedIntent);
switch (requestCode) {
case (BROWSE_RESULT_CODE):
- // Don't do anything if the user pressed back from the file picker.
+ // Only do something if the user didn't press back from the file picker.
if (resultCode == Activity.RESULT_OK) {
- // Get a handle for the views.
- EditText fileNameEditText = findViewById(R.id.file_name_edittext);
- TextView fileExistsWarningTextView = findViewById(R.id.file_exists_warning_textview);
-
- // Instantiate the file name helper.
- FileNameHelper fileNameHelper = new FileNameHelper();
-
// Get the file path URI from the intent.
- Uri filePathUri = intent.getData();
+ Uri fileNameUri = returnedIntent.getData();
- // Use the file path from the intent if it exists.
- if (filePathUri != null) {
- // Convert the file name URI to a file name path.
- String fileNamePath = fileNameHelper.convertUriToFileNamePath(filePathUri);
+ // Get the file name string from the URI.
+ String fileNameString = fileNameUri.toString();
- // Set the file name path as the text of the file name edit text.
- fileNameEditText.setText(fileNamePath);
+ // Set the file name name text.
+ fileNameEditText.setText(fileNameString);
- // Hide the file exists warning text view, because the file picker will have just created a file if export was selected.
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNameString.length());
}
break;
- case OPENPGP_EXPORT_RESULT_CODE:
- // Get the temporary unencrypted export file.
- File temporaryUnencryptedExportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.settings) + " " + BuildConfig.VERSION_NAME + ".pbs");
+ case OPENPGP_IMPORT_RESULT_CODE:
+ // Delete the temporary PGP encrypted import file.
+ if (temporaryPgpEncryptedImportFile.exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ temporaryPgpEncryptedImportFile.delete();
+ }
+ break;
- // Delete the temporary unencrypted export file if it exists.
- if (temporaryUnencryptedExportFile.exists()) {
+ case OPENPGP_EXPORT_RESULT_CODE:
+ // Delete the temporary pre-encrypted export file if it exists.
+ if (temporaryPreEncryptedExportFile.exists()) {
//noinspection ResultOfMethodCallIgnored
- temporaryUnencryptedExportFile.delete();
+ temporaryPreEncryptedExportFile.delete();
}
break;
}
}
- private void exportSettings() {
- // Get a handle for the views.
- Spinner encryptionSpinner = findViewById(R.id.encryption_spinner);
- EditText fileNameEditText = findViewById(R.id.file_name_edittext);
-
+ public void importExport(View view) {
// Instantiate the import export database helper.
ImportExportDatabaseHelper importExportDatabaseHelper = new ImportExportDatabaseHelper();
- // Get the export file string.
- String exportFileString = fileNameEditText.getText().toString();
+ // Check to see if import or export is selected.
+ if (importRadioButton.isChecked()) { // Import is selected.
+ // Initialize the import status string
+ String importStatus = "";
- // Get the export and temporary unencrypted export files.
- File exportFile = new File(exportFileString);
- File temporaryUnencryptedExportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.settings) + " " + BuildConfig.VERSION_NAME + ".pbs");
+ // Get the file name string.
+ String fileNameString = fileNameEditText.getText().toString();
- // Create an export status string.
- String exportStatus;
+ // Import according to the encryption type.
+ switch (encryptionSpinner.getSelectedItemPosition()) {
+ case NO_ENCRYPTION:
+ try {
+ // Get an input stream for the file name.
+ InputStream inputStream = getContentResolver().openInputStream(Uri.parse(fileNameString));
+
+ // Import the unencrypted file.
+ importStatus = importExportDatabaseHelper.importUnencrypted(inputStream, this);
+ } catch (FileNotFoundException exception) {
+ // Update the import status.
+ importStatus = exception.toString();
+ }
- // Export according to the encryption type.
- switch (encryptionSpinner.getSelectedItemPosition()) {
- case NO_ENCRYPTION:
- // Export the unencrypted file.
- exportStatus = importExportDatabaseHelper.exportUnencrypted(exportFile, this);
+ // Restart Privacy Browser if successful.
+ if (importStatus.equals(ImportExportDatabaseHelper.IMPORT_SUCCESSFUL)) {
+ restartPrivacyBrowser();
+ }
+ break;
- // Show a disposition snackbar.
- if (exportStatus.equals(ImportExportDatabaseHelper.EXPORT_SUCCESSFUL)) {
- Snackbar.make(fileNameEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show();
- } else {
- Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + exportStatus, Snackbar.LENGTH_INDEFINITE).show();
- }
- break;
+ case PASSWORD_ENCRYPTION:
+ try {
+ // Get the encryption password.
+ String encryptionPasswordString = encryptionPasswordEditText.getText().toString();
- case PASSWORD_ENCRYPTION:
- // Create an unencrypted export in a private directory.
- exportStatus = importExportDatabaseHelper.exportUnencrypted(temporaryUnencryptedExportFile, this);
+ // Get an input stream for the file name.
+ InputStream inputStream = getContentResolver().openInputStream(Uri.parse(fileNameString));
- try {
- // Create an unencrypted export file input stream.
- FileInputStream unencryptedExportFileInputStream = new FileInputStream(temporaryUnencryptedExportFile);
+ // Get the salt from the beginning of the import file.
+ byte[] saltByteArray = new byte[32];
+ //noinspection ResultOfMethodCallIgnored
+ inputStream.read(saltByteArray);
- // Delete the encrypted export file if it exists.
- if (exportFile.exists()) {
+ // Get the initialization vector from the import file.
+ byte[] initializationVector = new byte[12];
//noinspection ResultOfMethodCallIgnored
- exportFile.delete();
- }
+ inputStream.read(initializationVector);
- // Create an encrypted export file output stream.
- FileOutputStream encryptedExportFileOutputStream = new FileOutputStream(exportFile);
+ // Convert the encryption password to a byte array.
+ byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes(StandardCharsets.UTF_8);
- // Get a handle for the encryption password EditText.
- EditText encryptionPasswordEditText = findViewById(R.id.password_encryption_edittext);
+ // Append the salt to the encryption password byte array. This protects against rainbow table attacks.
+ byte[] encryptionPasswordWithSaltByteArray = new byte[encryptionPasswordByteArray.length + saltByteArray.length];
+ System.arraycopy(encryptionPasswordByteArray, 0, encryptionPasswordWithSaltByteArray, 0, encryptionPasswordByteArray.length);
+ System.arraycopy(saltByteArray, 0, encryptionPasswordWithSaltByteArray, encryptionPasswordByteArray.length, saltByteArray.length);
- // Get the encryption password.
- String encryptionPasswordString = encryptionPasswordEditText.getText().toString();
+ // Get a SHA-512 message digest.
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
- // Initialize a secure random number generator.
- SecureRandom secureRandom = new SecureRandom();
+ // Hash the salted encryption password. Otherwise, any characters after the 32nd character in the password are ignored.
+ byte[] hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray);
- // Get a 256 bit (32 byte) random salt.
- byte[] saltByteArray = new byte[32];
- secureRandom.nextBytes(saltByteArray);
+ // Truncate the encryption password byte array to 256 bits (32 bytes).
+ byte[] truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32);
- // Convert the encryption password to a byte array.
- byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes(StandardCharsets.UTF_8);
+ // Create an AES secret key from the encryption password byte array.
+ SecretKeySpec secretKey = new SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES");
- // Append the salt to the encryption password byte array. This protects against rainbow table attacks.
- byte[] encryptionPasswordWithSaltByteArray = new byte[encryptionPasswordByteArray.length + saltByteArray.length];
- System.arraycopy(encryptionPasswordByteArray, 0, encryptionPasswordWithSaltByteArray, 0, encryptionPasswordByteArray.length);
- System.arraycopy(saltByteArray, 0, encryptionPasswordWithSaltByteArray, encryptionPasswordByteArray.length, saltByteArray.length);
+ // Get a Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext. It doesn't use padding.
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
- // Get a SHA-512 message digest.
- MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
+ // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector.
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
- // Hash the salted encryption password. Otherwise, any characters after the 32nd character in the password are ignored.
- byte[] hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray);
+ // Initialize the cipher.
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
- // Truncate the encryption password byte array to 256 bits (32 bytes).
- byte[] truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32);
+ // Create a cipher input stream.
+ CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
- // Create an AES secret key from the encryption password byte array.
- SecretKeySpec secretKey = new SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES");
+ // Initialize variables to store data as it is moved from the cipher input stream to the unencrypted import file output stream. Move 128 bits (16 bytes) at a time.
+ int numberOfBytesRead;
+ byte[] decryptedBytes = new byte[16];
- // Generate a random 12 byte initialization vector. According to NIST, a 12 byte initialization vector is more secure than a 16 byte one.
- byte[] initializationVector = new byte[12];
- secureRandom.nextBytes(initializationVector);
- // Get a Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext. It doesn't use padding.
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+ // Create a private temporary unencrypted import file.
+ File temporaryUnencryptedImportFile = File.createTempFile("temporary_unencrypted_import_file", null, getApplicationContext().getCacheDir());
- // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector.
- GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
+ // Create an temporary unencrypted import file output stream.
+ FileOutputStream temporaryUnencryptedImportFileOutputStream = new FileOutputStream(temporaryUnencryptedImportFile);
- // Initialize the cipher.
- cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
- // Add the salt and the initialization vector to the export file.
- encryptedExportFileOutputStream.write(saltByteArray);
- encryptedExportFileOutputStream.write(initializationVector);
+ // Read up to 128 bits (16 bytes) of data from the cipher input stream. `-1` will be returned when the end fo the file is reached.
+ while ((numberOfBytesRead = cipherInputStream.read(decryptedBytes)) != -1) {
+ // Write the data to the temporary unencrypted import file output stream.
+ temporaryUnencryptedImportFileOutputStream.write(decryptedBytes, 0, numberOfBytesRead);
+ }
- // Create a cipher output stream.
- CipherOutputStream cipherOutputStream = new CipherOutputStream(encryptedExportFileOutputStream, cipher);
- // Initialize variables to store data as it is moved from the unencrypted export file input stream to the cipher output stream. Move 128 bits (16 bytes) at a time.
- int numberOfBytesRead;
- byte[] encryptedBytes = new byte[16];
+ // Flush the temporary unencrypted import file output stream.
+ temporaryUnencryptedImportFileOutputStream.flush();
- // Read up to 128 bits (16 bytes) of data from the unencrypted export file stream. `-1` will be returned when the end of the file is reached.
- while ((numberOfBytesRead = unencryptedExportFileInputStream.read(encryptedBytes)) != -1) {
- // Write the data to the cipher output stream.
- cipherOutputStream.write(encryptedBytes, 0, numberOfBytesRead);
- }
+ // Close the streams.
+ temporaryUnencryptedImportFileOutputStream.close();
+ cipherInputStream.close();
+ inputStream.close();
- // Close the streams.
- cipherOutputStream.flush();
- cipherOutputStream.close();
- encryptedExportFileOutputStream.close();
- unencryptedExportFileInputStream.close();
-
- // Wipe the encryption data from memory.
- //noinspection UnusedAssignment
- encryptionPasswordString = "";
- Arrays.fill(saltByteArray, (byte) 0);
- Arrays.fill(encryptionPasswordByteArray, (byte) 0);
- Arrays.fill(encryptionPasswordWithSaltByteArray, (byte) 0);
- Arrays.fill(hashedEncryptionPasswordWithSaltByteArray, (byte) 0);
- Arrays.fill(truncatedHashedEncryptionPasswordWithSaltByteArray, (byte) 0);
- Arrays.fill(initializationVector, (byte) 0);
- Arrays.fill(encryptedBytes, (byte) 0);
-
- // Delete the temporary unencrypted export file.
- //noinspection ResultOfMethodCallIgnored
- temporaryUnencryptedExportFile.delete();
- } catch (Exception exception) {
- exportStatus = exception.toString();
- }
+ // Wipe the encryption data from memory.
+ //noinspection UnusedAssignment
+ encryptionPasswordString = "";
+ Arrays.fill(saltByteArray, (byte) 0);
+ Arrays.fill(initializationVector, (byte) 0);
+ Arrays.fill(encryptionPasswordByteArray, (byte) 0);
+ Arrays.fill(encryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(hashedEncryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(truncatedHashedEncryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(decryptedBytes, (byte) 0);
- // Show a disposition snackbar.
- if (exportStatus.equals(ImportExportDatabaseHelper.EXPORT_SUCCESSFUL)) {
- Snackbar.make(fileNameEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show();
- } else {
- Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + exportStatus, Snackbar.LENGTH_INDEFINITE).show();
- }
- break;
+ // Create a temporary unencrypted import file input stream.
+ FileInputStream temporaryUnencryptedImportFileInputStream = new FileInputStream(temporaryUnencryptedImportFile);
- case OPENPGP_ENCRYPTION:
- // Create an unencrypted export in the private location.
- importExportDatabaseHelper.exportUnencrypted(temporaryUnencryptedExportFile, this);
+ // Import the temporary unencrypted import file.
+ importStatus = importExportDatabaseHelper.importUnencrypted(temporaryUnencryptedImportFileInputStream, this);
- // Create an encryption intent for OpenKeychain.
- Intent openKeychainEncryptIntent = new Intent("org.sufficientlysecure.keychain.action.ENCRYPT_DATA");
+ // Close the temporary unencrypted import file input stream.
+ temporaryUnencryptedImportFileInputStream.close();
- // Include the temporary unencrypted export file URI.
- openKeychainEncryptIntent.setData(FileProvider.getUriForFile(this, getString(R.string.file_provider), temporaryUnencryptedExportFile));
+ // Delete the temporary unencrypted import file.
+ //noinspection ResultOfMethodCallIgnored
+ temporaryUnencryptedImportFile.delete();
- // Allow OpenKeychain to read the file URI.
- openKeychainEncryptIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ // Restart Privacy Browser if successful.
+ if (importStatus.equals(ImportExportDatabaseHelper.IMPORT_SUCCESSFUL)) {
+ restartPrivacyBrowser();
+ }
+ } catch (Exception exception) {
+ // Update the import status.
+ importStatus = exception.toString();
+ }
+ break;
- // Send the intent to the OpenKeychain package.
- openKeychainEncryptIntent.setPackage("org.sufficientlysecure.keychain");
+ case OPENPGP_ENCRYPTION:
+ try {
+ // Set the temporary PGP encrypted import file.
+ temporaryPgpEncryptedImportFile = File.createTempFile("temporary_pgp_encrypted_import_file", null, getApplicationContext().getCacheDir());
- // Make it so.
- startActivityForResult(openKeychainEncryptIntent, OPENPGP_EXPORT_RESULT_CODE);
- break;
- }
+ // Create a temporary PGP encrypted import file output stream.
+ FileOutputStream temporaryPgpEncryptedImportFileOutputStream = new FileOutputStream(temporaryPgpEncryptedImportFile);
- // 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);
- }
+ // Get an input stream for the file name.
+ InputStream inputStream = getContentResolver().openInputStream(Uri.parse(fileNameString));
- private void importSettings() {
- // Get a handle for the views.
- Spinner encryptionSpinner = findViewById(R.id.encryption_spinner);
- EditText fileNameEditText = findViewById(R.id.file_name_edittext);
+ // Create a transfer byte array.
+ byte[] transferByteArray = new byte[1024];
- // Instantiate the import export database helper.
- ImportExportDatabaseHelper importExportDatabaseHelper = new ImportExportDatabaseHelper();
+ // Create an integer to track the number of bytes read.
+ int bytesRead;
- // Get the import file.
- File importFile = new File(fileNameEditText.getText().toString());
+ // Copy the input stream to the temporary PGP encrypted import file.
+ while ((bytesRead = inputStream.read(transferByteArray)) > 0) {
+ temporaryPgpEncryptedImportFileOutputStream.write(transferByteArray, 0, bytesRead);
+ }
- // Initialize the import status string
- String importStatus = "";
+ // Flush the temporary PGP encrypted import file output stream.
+ temporaryPgpEncryptedImportFileOutputStream.flush();
- // Import according to the encryption type.
- switch (encryptionSpinner.getSelectedItemPosition()) {
- case NO_ENCRYPTION:
- // Import the unencrypted file.
- importStatus = importExportDatabaseHelper.importUnencrypted(importFile, this);
- break;
+ // Close the streams.
+ inputStream.close();
+ temporaryPgpEncryptedImportFileOutputStream.flush();
- case PASSWORD_ENCRYPTION:
- // Use a private temporary import location.
- File temporaryUnencryptedImportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.settings) + " " + BuildConfig.VERSION_NAME + ".pbs");
- try {
- // Create an encrypted import file input stream.
- FileInputStream encryptedImportFileInputStream = new FileInputStream(importFile);
+ // Create an decryption intent for OpenKeychain.
+ Intent openKeychainDecryptIntent = new Intent("org.sufficientlysecure.keychain.action.DECRYPT_DATA");
- // Delete the temporary import file if it exists.
- if (temporaryUnencryptedImportFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- temporaryUnencryptedImportFile.delete();
+ // Include the URI to be decrypted.
+ openKeychainDecryptIntent.setData(FileProvider.getUriForFile(this, getString(R.string.file_provider), temporaryPgpEncryptedImportFile));
+
+ // Allow OpenKeychain to read the file URI.
+ openKeychainDecryptIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // Send the intent to the OpenKeychain package.
+ openKeychainDecryptIntent.setPackage("org.sufficientlysecure.keychain");
+
+ // Make it so.
+ startActivityForResult(openKeychainDecryptIntent, OPENPGP_IMPORT_RESULT_CODE);
+
+ // Update the import status.
+ importStatus = ImportExportDatabaseHelper.IMPORT_SUCCESSFUL;
+ } catch (Exception exception) {
+ // Update the import status.
+ importStatus = exception.toString();
}
+ break;
+ }
- // Create an unencrypted import file output stream.
- FileOutputStream unencryptedImportFileOutputStream = new FileOutputStream(temporaryUnencryptedImportFile);
+ // Respond to the import status.
+ if (!importStatus.equals(ImportExportDatabaseHelper.IMPORT_SUCCESSFUL)) {
+ // Display a snack bar with the import error.
+ Snackbar.make(fileNameEditText, getString(R.string.import_failed) + " " + importStatus, Snackbar.LENGTH_INDEFINITE).show();
+ }
+ } else { // Export is selected.
+ // Export according to the encryption type.
+ switch (encryptionSpinner.getSelectedItemPosition()) {
+ case NO_ENCRYPTION:
+ // Get the file name string.
+ String noEncryptionFileNameString = fileNameEditText.getText().toString();
+
+ try {
+ // Get the export file output stream.
+ OutputStream exportFileOutputStream = getContentResolver().openOutputStream(Uri.parse(noEncryptionFileNameString));
+
+ // Export the unencrypted file.
+ String noEncryptionExportStatus = importExportDatabaseHelper.exportUnencrypted(exportFileOutputStream, this);
+
+ // Display an export disposition snackbar.
+ if (noEncryptionExportStatus.equals(ImportExportDatabaseHelper.EXPORT_SUCCESSFUL)) {
+ Snackbar.make(fileNameEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show();
+ } else {
+ Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + noEncryptionExportStatus, Snackbar.LENGTH_INDEFINITE).show();
+ }
+ } catch (FileNotFoundException fileNotFoundException) {
+ // Display a snackbar with the exception.
+ Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + fileNotFoundException, Snackbar.LENGTH_INDEFINITE).show();
+ }
+ break;
- // Get a handle for the encryption password EditText.
- EditText encryptionPasswordEditText = findViewById(R.id.password_encryption_edittext);
+ case PASSWORD_ENCRYPTION:
+ try {
+ // Create a temporary unencrypted export file.
+ File temporaryUnencryptedExportFile = File.createTempFile("temporary_unencrypted_export_file", null, getApplicationContext().getCacheDir());
- // Get the encryption password.
- String encryptionPasswordString = encryptionPasswordEditText.getText().toString();
+ // Create a temporary unencrypted export output stream.
+ FileOutputStream temporaryUnencryptedExportOutputStream = new FileOutputStream(temporaryUnencryptedExportFile);
- // Get the salt from the beginning of the import file.
- byte[] saltByteArray = new byte[32];
- //noinspection ResultOfMethodCallIgnored
- encryptedImportFileInputStream.read(saltByteArray);
+ // Populate the temporary unencrypted export.
+ String passwordEncryptionExportStatus = importExportDatabaseHelper.exportUnencrypted(temporaryUnencryptedExportOutputStream, this);
- // Get the initialization vector from the import file.
- byte[] initializationVector = new byte[12];
- //noinspection ResultOfMethodCallIgnored
- encryptedImportFileInputStream.read(initializationVector);
+ // Close the temporary unencrypted export output stream.
+ temporaryUnencryptedExportOutputStream.close();
- // Convert the encryption password to a byte array.
- byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes(StandardCharsets.UTF_8);
+ // Create an unencrypted export file input stream.
+ FileInputStream unencryptedExportFileInputStream = new FileInputStream(temporaryUnencryptedExportFile);
- // Append the salt to the encryption password byte array. This protects against rainbow table attacks.
- byte[] encryptionPasswordWithSaltByteArray = new byte[encryptionPasswordByteArray.length + saltByteArray.length];
- System.arraycopy(encryptionPasswordByteArray, 0, encryptionPasswordWithSaltByteArray, 0, encryptionPasswordByteArray.length);
- System.arraycopy(saltByteArray, 0, encryptionPasswordWithSaltByteArray, encryptionPasswordByteArray.length, saltByteArray.length);
+ // Get the encryption password.
+ String encryptionPasswordString = encryptionPasswordEditText.getText().toString();
- // Get a SHA-512 message digest.
- MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
+ // Initialize a secure random number generator.
+ SecureRandom secureRandom = new SecureRandom();
- // Hash the salted encryption password. Otherwise, any characters after the 32nd character in the password are ignored.
- byte[] hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray);
+ // Get a 256 bit (32 byte) random salt.
+ byte[] saltByteArray = new byte[32];
+ secureRandom.nextBytes(saltByteArray);
- // Truncate the encryption password byte array to 256 bits (32 bytes).
- byte[] truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32);
+ // Convert the encryption password to a byte array.
+ byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes(StandardCharsets.UTF_8);
- // Create an AES secret key from the encryption password byte array.
- SecretKeySpec secretKey = new SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES");
+ // Append the salt to the encryption password byte array. This protects against rainbow table attacks.
+ byte[] encryptionPasswordWithSaltByteArray = new byte[encryptionPasswordByteArray.length + saltByteArray.length];
+ System.arraycopy(encryptionPasswordByteArray, 0, encryptionPasswordWithSaltByteArray, 0, encryptionPasswordByteArray.length);
+ System.arraycopy(saltByteArray, 0, encryptionPasswordWithSaltByteArray, encryptionPasswordByteArray.length, saltByteArray.length);
- // Get a Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext. It doesn't use padding.
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+ // Get a SHA-512 message digest.
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
- // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector.
- GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
+ // Hash the salted encryption password. Otherwise, any characters after the 32nd character in the password are ignored.
+ byte[] hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray);
- // Initialize the cipher.
- cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
+ // Truncate the encryption password byte array to 256 bits (32 bytes).
+ byte[] truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32);
- // Create a cipher input stream.
- CipherInputStream cipherInputStream = new CipherInputStream(encryptedImportFileInputStream, cipher);
+ // Create an AES secret key from the encryption password byte array.
+ SecretKeySpec secretKey = new SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES");
- // Initialize variables to store data as it is moved from the cipher input stream to the unencrypted import file output stream. Move 128 bits (16 bytes) at a time.
- int numberOfBytesRead;
- byte[] decryptedBytes = new byte[16];
+ // Generate a random 12 byte initialization vector. According to NIST, a 12 byte initialization vector is more secure than a 16 byte one.
+ byte[] initializationVector = new byte[12];
+ secureRandom.nextBytes(initializationVector);
- // Read up to 128 bits (16 bytes) of data from the cipher input stream. `-1` will be returned when the end fo the file is reached.
- while ((numberOfBytesRead = cipherInputStream.read(decryptedBytes)) != -1) {
- // Write the data to the unencrypted import file output stream.
- unencryptedImportFileOutputStream.write(decryptedBytes, 0, numberOfBytesRead);
- }
+ // Get a Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext. It doesn't use padding.
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
- // Close the streams.
- unencryptedImportFileOutputStream.flush();
- unencryptedImportFileOutputStream.close();
- cipherInputStream.close();
- encryptedImportFileInputStream.close();
-
- // Wipe the encryption data from memory.
- //noinspection UnusedAssignment
- encryptionPasswordString = "";
- Arrays.fill(saltByteArray, (byte) 0);
- Arrays.fill(initializationVector, (byte) 0);
- Arrays.fill(encryptionPasswordByteArray, (byte) 0);
- Arrays.fill(encryptionPasswordWithSaltByteArray, (byte) 0);
- Arrays.fill(hashedEncryptionPasswordWithSaltByteArray, (byte) 0);
- Arrays.fill(truncatedHashedEncryptionPasswordWithSaltByteArray, (byte) 0);
- Arrays.fill(decryptedBytes, (byte) 0);
-
- // Import the unencrypted database from the private location.
- importStatus = importExportDatabaseHelper.importUnencrypted(temporaryUnencryptedImportFile, this);
-
- // Delete the temporary unencrypted import file.
- //noinspection ResultOfMethodCallIgnored
- temporaryUnencryptedImportFile.delete();
- } catch (Exception exception) {
- importStatus = exception.toString();
- }
- break;
+ // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector.
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
- case OPENPGP_ENCRYPTION:
- try {
- // Create an decryption intent for OpenKeychain.
- Intent openKeychainDecryptIntent = new Intent("org.sufficientlysecure.keychain.action.DECRYPT_DATA");
+ // Initialize the cipher.
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
- // Include the URI to be decrypted.
- openKeychainDecryptIntent.setData(FileProvider.getUriForFile(this, getString(R.string.file_provider), importFile));
+ // Get the file name string.
+ String passwordEncryptionFileNameString = fileNameEditText.getText().toString();
- // Allow OpenKeychain to read the file URI.
- openKeychainDecryptIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ // Get the export file output stream.
+ OutputStream exportFileOutputStream = getContentResolver().openOutputStream(Uri.parse(passwordEncryptionFileNameString));
- // Send the intent to the OpenKeychain package.
- openKeychainDecryptIntent.setPackage("org.sufficientlysecure.keychain");
+ // Add the salt and the initialization vector to the export file output stream.
+ exportFileOutputStream.write(saltByteArray);
+ exportFileOutputStream.write(initializationVector);
- // Make it so.
- startActivity(openKeychainDecryptIntent);
- } catch (IllegalArgumentException exception) { // The file import location is not valid.
- // Display a snack bar with the import error.
- Snackbar.make(fileNameEditText, getString(R.string.import_failed) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
- }
- break;
- }
+ // Create a cipher output stream.
+ CipherOutputStream cipherOutputStream = new CipherOutputStream(exportFileOutputStream, cipher);
+
+ // Initialize variables to store data as it is moved from the unencrypted export file input stream to the cipher output stream. Move 128 bits (16 bytes) at a time.
+ int numberOfBytesRead;
+ byte[] encryptedBytes = new byte[16];
+
+ // Read up to 128 bits (16 bytes) of data from the unencrypted export file stream. `-1` will be returned when the end of the file is reached.
+ while ((numberOfBytesRead = unencryptedExportFileInputStream.read(encryptedBytes)) != -1) {
+ // Write the data to the cipher output stream.
+ cipherOutputStream.write(encryptedBytes, 0, numberOfBytesRead);
+ }
+
+ // Close the streams.
+ cipherOutputStream.flush();
+ cipherOutputStream.close();
+ exportFileOutputStream.close();
+ unencryptedExportFileInputStream.close();
+
+ // Wipe the encryption data from memory.
+ //noinspection UnusedAssignment
+ encryptionPasswordString = "";
+ Arrays.fill(saltByteArray, (byte) 0);
+ Arrays.fill(encryptionPasswordByteArray, (byte) 0);
+ Arrays.fill(encryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(hashedEncryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(truncatedHashedEncryptionPasswordWithSaltByteArray, (byte) 0);
+ Arrays.fill(initializationVector, (byte) 0);
+ Arrays.fill(encryptedBytes, (byte) 0);
+
+ // Delete the temporary unencrypted export file.
+ //noinspection ResultOfMethodCallIgnored
+ temporaryUnencryptedExportFile.delete();
+
+ // Display an export disposition snackbar.
+ if (passwordEncryptionExportStatus.equals(ImportExportDatabaseHelper.EXPORT_SUCCESSFUL)) {
+ Snackbar.make(fileNameEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show();
+ } else {
+ Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + passwordEncryptionExportStatus, Snackbar.LENGTH_INDEFINITE).show();
+ }
+ } catch (Exception exception) {
+ // Display a snackbar with the exception.
+ Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
+ }
+ break;
- // Respond to the import disposition.
- if (importStatus.equals(ImportExportDatabaseHelper.IMPORT_SUCCESSFUL)) { // The import was successful.
- // Create an intent to restart Privacy Browser.
- Intent restartIntent = getParentActivityIntent();
+ case OPENPGP_ENCRYPTION:
+ try {
+ // Set the temporary pre-encrypted export file.
+ temporaryPreEncryptedExportFile = new File(getApplicationContext().getCacheDir() + "/" + getString(R.string.settings) + " " + BuildConfig.VERSION_NAME + ".pbs");
- // Assert that the intent is not null to remove the lint error below.
- assert restartIntent != null;
+ // Delete the temporary pre-encrypted export file if it already exists.
+ if (temporaryPreEncryptedExportFile.exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ temporaryPreEncryptedExportFile.delete();
+ }
- // `Intent.FLAG_ACTIVITY_CLEAR_TASK` removes all activities from the stack. It requires `Intent.FLAG_ACTIVITY_NEW_TASK`.
- restartIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ // Create a temporary pre-encrypted export output stream.
+ FileOutputStream temporaryPreEncryptedExportOutputStream = new FileOutputStream(temporaryPreEncryptedExportFile);
+
+ // Populate the temporary pre-encrypted export file.
+ String openpgpEncryptionExportStatus = importExportDatabaseHelper.exportUnencrypted(temporaryPreEncryptedExportOutputStream, this);
+
+ // Flush the temporary pre-encryption export output stream.
+ temporaryPreEncryptedExportOutputStream.flush();
+
+ // Close the temporary pre-encryption export output stream.
+ temporaryPreEncryptedExportOutputStream.close();
+
+ // Display an export error snackbar if the temporary pre-encrypted export failed.
+ if (!openpgpEncryptionExportStatus.equals(ImportExportDatabaseHelper.EXPORT_SUCCESSFUL)) {
+ Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + openpgpEncryptionExportStatus, Snackbar.LENGTH_INDEFINITE).show();
+ }
- // Create a restart handler.
- Handler restartHandler = new Handler();
+ // Create an encryption intent for OpenKeychain.
+ Intent openKeychainEncryptIntent = new Intent("org.sufficientlysecure.keychain.action.ENCRYPT_DATA");
- // Create a restart runnable.
- Runnable restartRunnable = () -> {
- // Restart Privacy Browser.
- startActivity(restartIntent);
+ // Include the temporary unencrypted export file URI.
+ openKeychainEncryptIntent.setData(FileProvider.getUriForFile(this, getString(R.string.file_provider), temporaryPreEncryptedExportFile));
- // Kill this instance of Privacy Browser. Otherwise, the app exhibits sporadic behavior after the restart.
- System.exit(0);
- };
+ // Allow OpenKeychain to read the file URI.
+ openKeychainEncryptIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- // Restart Privacy Browser after 150 milliseconds to allow enough time for the preferences to be saved.
- restartHandler.postDelayed(restartRunnable, 150);
+ // Send the intent to the OpenKeychain package.
+ openKeychainEncryptIntent.setPackage("org.sufficientlysecure.keychain");
- } else if (!(encryptionSpinner.getSelectedItemPosition() == OPENPGP_ENCRYPTION)){ // The import was not successful.
- // Display a snack bar with the import error.
- Snackbar.make(fileNameEditText, getString(R.string.import_failed) + " " + importStatus, Snackbar.LENGTH_INDEFINITE).show();
+ // Make it so.
+ startActivityForResult(openKeychainEncryptIntent, OPENPGP_EXPORT_RESULT_CODE);
+ } catch (Exception exception) {
+ // Display a snackbar with the exception.
+ Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
+ }
+ break;
+ }
}
}
+
+ private void restartPrivacyBrowser() {
+ // Create an intent to restart Privacy Browser.
+ Intent restartIntent = getParentActivityIntent();
+
+ // Assert that the intent is not null to remove the lint error below.
+ assert restartIntent != null;
+
+ // `Intent.FLAG_ACTIVITY_CLEAR_TASK` removes all activities from the stack. It requires `Intent.FLAG_ACTIVITY_NEW_TASK`.
+ restartIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ // Create a restart handler.
+ Handler restartHandler = new Handler();
+
+ // Create a restart runnable.
+ Runnable restartRunnable = () -> {
+ // Restart Privacy Browser.
+ startActivity(restartIntent);
+
+ // Kill this instance of Privacy Browser. Otherwise, the app exhibits sporadic behavior after the restart.
+ System.exit(0);
+ };
+
+ // Restart Privacy Browser after 150 milliseconds to allow enough time for the preferences to be saved.
+ restartHandler.postDelayed(restartRunnable, 150);
+ }
}
\ No newline at end of file
/*
- * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
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.ContentResolver;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.media.MediaScannerConnection;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ScrollView;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.FileProvider;
import androidx.fragment.app.DialogFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.stoutner.privacybrowser.R;
import com.stoutner.privacybrowser.asynctasks.GetLogcat;
-import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
import com.stoutner.privacybrowser.dialogs.SaveDialog;
-import com.stoutner.privacybrowser.helpers.FileNameHelper;
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.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
-public class LogcatActivity extends AppCompatActivity implements SaveDialog.SaveListener, StoragePermissionDialog.StoragePermissionDialogListener {
+public class LogcatActivity extends AppCompatActivity implements SaveDialog.SaveListener {
// Declare the class constants.
private final String SCROLLVIEW_POSITION = "scrollview_position";
- // Declare the class variables.
- private String filePathString;
-
// Define the class views.
private TextView logcatTextView;
// Set the content view.
setContentView(R.layout.logcat_coordinatorlayout);
- // Set the toolbar as the action bar.
+ // Get handles for the views.
Toolbar toolbar = findViewById(R.id.logcat_toolbar);
+ SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.logcat_swiperefreshlayout);
+
+ // Set the toolbar as the action bar.
setSupportActionBar(toolbar);
// Get a handle for the action bar.
logcatTextView = findViewById(R.id.logcat_textview);
// Implement swipe to refresh.
- SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.logcat_swiperefreshlayout);
swipeRefreshLayout.setOnRefreshListener(() -> {
// Get the current logcat.
new GetLogcat(this, 0).execute();
savedInstanceState.putInt(SCROLLVIEW_POSITION, scrollViewYPositionInt);
}
- @Override
- public void onSave(int saveType, DialogFragment dialogFragment) {
- // Get a handle for the dialog.
- Dialog dialog = dialogFragment.getDialog();
-
- // Remove the lint warning below that the dialog might be null.
- assert dialog != null;
-
- // Get a handle for the file name edit text.
- EditText fileNameEditText = dialog.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 is 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. The type is specified as `0` because it currently isn't used for this activity.
- DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(0);
-
- // 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 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 onCloseStoragePermissionDialog(int requestType) {
- // 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.
- // Display an error snackbar.
- Snackbar.make(logcatTextView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).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) {
+ public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
// Run the default commands.
- super.onActivityResult(requestCode, resultCode, data);
+ super.onActivityResult(requestCode, resultCode, returnedIntent);
// Only do something if the user didn't press back from the file picker.
if (resultCode == Activity.RESULT_OK) {
// Remove the lint warning below that the save dialog might be null.
assert saveDialog != null;
- // Get a handle for the dialog views.
+ // Get a handle for the file name edit text.
EditText fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext);
- TextView fileExistsWarningTextView = saveDialog.findViewById(R.id.file_exists_warning_textview);
// Get the file name URI from the intent.
- Uri fileNameUri = data.getData();
+ Uri fileNameUri = returnedIntent.getData();
- // Process the file name URI if it is not null.
- if (fileNameUri != null) {
- // Instantiate a file name helper.
- FileNameHelper fileNameHelper = new FileNameHelper();
+ // Get the file name string from the URI.
+ String fileNameString = fileNameUri.toString();
- // Convert the file name URI to a file name path.
- String fileNamePath = fileNameHelper.convertUriToFileNamePath(fileNameUri);
+ // Set the file name text.
+ fileNameEditText.setText(fileNameString);
- // Set the file name path as the text of the file name edit text.
- fileNameEditText.setText(fileNamePath);
-
- // Move the cursor to the end of the file name edit text.
- fileNameEditText.setSelection(fileNamePath.length());
-
- // Hide the file exists warning.
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNameString.length());
}
}
}
- private void saveLogcat(String fileNameString) {
+ @Override
+ public void onSave(int saveType, DialogFragment dialogFragment) {
+ // Get a handle for the dialog.
+ Dialog dialog = dialogFragment.getDialog();
+
+ // Remove the lint warning below that the dialog might be null.
+ assert dialog != null;
+
+ // Get a handle for the file name edit text.
+ EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
+
+ // Get the file path string.
+ String fileNameString = fileNameEditText.getText().toString();
+
try {
// Get the logcat as a string.
String logcatString = logcatTextView.getText().toString();
// 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);
-
- // Delete the file if it already exists.
- if (saveFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- saveFile.delete();
- }
+ // Open an output stream.
+ OutputStream outputStream = getContentResolver().openOutputStream(Uri.parse(fileNameString));
// Create a file buffered writer.
- BufferedWriter fileBufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(saveFile)));
+ BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
// 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);
+ bufferedWriter.append(transferString);
// Append a line break.
- fileBufferedWriter.append("\n");
+ bufferedWriter.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);
-
- // Create a logcat saved snackbar.
- Snackbar logcatSavedSnackbar = Snackbar.make(logcatTextView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT);
-
- // Add an open action to the snackbar.
- logcatSavedSnackbar.setAction(R.string.open, (View view) -> {
- // Get a file for the file name string.
- File file = new File(fileNameString);
+ // Flush the buffered writer.
+ bufferedWriter.flush();
- // Declare a file URI variable.
- Uri fileUri;
-
- // Get the URI for the file according to the Android version.
- if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
- fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file);
- } else { // Get the raw file path URI.
- fileUri = Uri.fromFile(file);
- }
-
- // Get a handle for the content resolver.
- ContentResolver contentResolver = getContentResolver();
-
- // Create an open intent with `ACTION_VIEW`.
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
-
- // Set the URI and the MIME type.
- openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
-
- // Allow the app to read the file URI.
- openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Show the chooser.
- startActivity(Intent.createChooser(openIntent, getString(R.string.open)));
- });
+ // Close the inputs and outputs.
+ logcatBufferedReader.close();
+ logcatInputStream.close();
+ bufferedWriter.close();
+ outputStream.close();
- // Show the logcat saved snackbar.
- logcatSavedSnackbar.show();
+ // Display a snackbar with the saved logcat information.
+ Snackbar.make(logcatTextView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show();
} catch (Exception exception) {
// Display a snackbar with the error message.
Snackbar.make(logcatTextView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
package com.stoutner.privacybrowser.activities;
-import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipboardManager;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.FileProvider;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
-import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
import com.stoutner.privacybrowser.helpers.BlocklistHelper;
import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
-import com.stoutner.privacybrowser.helpers.FileNameHelper;
import com.stoutner.privacybrowser.helpers.ProxyHelper;
import com.stoutner.privacybrowser.views.NestedScrollWebView;
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
- PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
- UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
+ PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, UrlHistoryDialog.NavigateHistoryListener,
+ WebViewTabFragment.NewTabListener {
// The executor service handles background tasks. It is accessed from `ViewSourceActivity`.
public static ExecutorService executorService = Executors.newFixedThreadPool(4);
public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
- // Start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
- public final static int BROWSE_OPEN_REQUEST_CODE = 0;
- public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1;
- private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2;
+ // Define the start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
+ private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
+ public final static int BROWSE_OPEN_REQUEST_CODE = 1;
+ public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 2;
// The proxy mode is public static so it can be accessed from `ProxyHelper()`.
// It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
private boolean sanitizeFacebookClickIds;
private boolean sanitizeTwitterAmpRedirects;
- // The file path strings are used in `onSaveWebpage()` and `onRequestPermissionResult()`
- private String openFilePath;
- private String saveWebpageUrl;
- private String saveWebpageFilePath;
-
// Declare the class views.
private FrameLayout rootFrameLayout;
private DrawerLayout drawerLayout;
return true;
} else if (menuItemId == R.id.save_url) { // Save URL.
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
- // Consume the event.
- return true;
- } else if (menuItemId == R.id.save_archive) { // Save archive.
- // Instantiate the save dialog.
- DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE, null, null, getString(R.string.webpage_mht), null,
- false);
-
- // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
- saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
-
// Consume the event.
return true;
} else if (menuItemId == R.id.save_image) { // Save image.
// Instantiate the save dialog.
- DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null,
+ DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null,
false);
// Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
// Add a Save URL entry.
menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
// Consume the event.
// Add a Save Image entry.
menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
// Consume the event.
// Add a Save Image entry.
menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
// Consume the event.
// Add a Save URL entry.
menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
// Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
- new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
// Consume the event.
}
break;
- case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
+ case BROWSE_OPEN_REQUEST_CODE:
// 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 saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
+ // Get a handle for the open dialog fragment.
+ DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
// Only update the file name if the dialog still exists.
- if (saveWebpageDialogFragment != null) {
- // Get a handle for the save webpage dialog.
- Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
+ if (openDialogFragment != null) {
+ // Get a handle for the open dialog.
+ Dialog openDialog = openDialogFragment.getDialog();
// Remove the incorrect lint warning below that the dialog might be null.
- assert saveWebpageDialog != null;
+ assert openDialog != null;
// Get a handle for the file name edit text.
- EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
- TextView fileExistsWarningTextView = saveWebpageDialog.findViewById(R.id.file_exists_warning_textview);
-
- // Instantiate the file name helper.
- FileNameHelper fileNameHelper = new FileNameHelper();
+ EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
- // Get the file path if it isn't null.
- if (returnedIntent.getData() != null) {
- // Convert the file name URI to a file name path.
- String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
+ // Get the file name URI from the intent.
+ Uri fileNameUri = returnedIntent.getData();
- // Set the file name path as the text of the file name edit text.
- fileNameEditText.setText(fileNamePath);
+ // Get the file name string from the URI.
+ String fileNameString = fileNameUri.toString();
- // Move the cursor to the end of the file name edit text.
- fileNameEditText.setSelection(fileNamePath.length());
+ // Set the file name text.
+ fileNameEditText.setText(fileNameString);
- // Hide the file exists warning.
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNameString.length());
}
}
break;
- case BROWSE_OPEN_REQUEST_CODE:
+ case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
// Don't do anything if the user pressed back from the file picker.
if (resultCode == Activity.RESULT_OK) {
- // Get a handle for the open dialog fragment.
- DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
+ // Get a handle for the save dialog fragment.
+ DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
// Only update the file name if the dialog still exists.
- if (openDialogFragment != null) {
- // Get a handle for the open dialog.
- Dialog openDialog = openDialogFragment.getDialog();
+ if (saveWebpageDialogFragment != null) {
+ // Get a handle for the save webpage dialog.
+ Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
// Remove the incorrect lint warning below that the dialog might be null.
- assert openDialog != null;
+ assert saveWebpageDialog != null;
// Get a handle for the file name edit text.
- EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
+ EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
- // Instantiate the file name helper.
- FileNameHelper fileNameHelper = new FileNameHelper();
+ // Get the file name URI from the intent.
+ Uri fileNameUri = returnedIntent.getData();
- // Get the file path if it isn't null.
- if (returnedIntent.getData() != null) {
- // Convert the file name URI to a file name path.
- String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
+ // Get the file name string from the URI.
+ String fileNameString = fileNameUri.toString();
- // Set the file name path as the text of the file name edit text.
- fileNameEditText.setText(fileNamePath);
+ // Set the file name text.
+ fileNameEditText.setText(fileNameString);
- // Move the cursor to the end of the file name edit text.
- fileNameEditText.setSelection(fileNamePath.length());
- }
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNameString.length());
}
}
break;
EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
// Get the file path string.
- openFilePath = fileNameEditText.getText().toString();
+ String openFilePath = fileNameEditText.getText().toString();
// Apply the domain settings. This resets the favorite icon and removes any domain settings.
- applyDomainSettings(currentWebView, "file://" + openFilePath, true, false, false);
-
- // 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.
- // Open the file.
- currentWebView.loadUrl("file://" + openFilePath);
- } 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 (openFilePath.startsWith(externalPrivateDirectory)) { // the file path is in the external private directory.
- // Open the file.
- currentWebView.loadUrl("file://" + openFilePath);
- } else { // The file path is 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 = StoragePermissionDialog.displayDialog(StoragePermissionDialog.OPEN);
-
- // Show the storage permission alert dialog. The permission will be requested the the dialog is closed.
- storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
- } else { // Show the permission request directly.
- // Request the write external storage permission. The file will be opened when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, StoragePermissionDialog.OPEN);
- }
- }
- }
+ applyDomainSettings(currentWebView, openFilePath, true, false, false);
+
+ // Open the file.
+ currentWebView.loadUrl(openFilePath);
}
@Override
EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext);
EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
+ // Define the save webpage URL.
+ String saveWebpageUrl;
+
// Store the URL.
if ((originalUrlString != null) && originalUrlString.startsWith("data:")) {
// Save the original URL.
}
// Get the file path from the edit text.
- saveWebpageFilePath = 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 webpage according to the save type.
- switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
- // Save the URL.
- new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
- break;
-
- case StoragePermissionDialog.SAVE_ARCHIVE:
- // Save the webpage archive.
- saveWebpageArchive(saveWebpageFilePath);
- break;
-
- case StoragePermissionDialog.SAVE_IMAGE:
- // Save the webpage image.
- new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
- break;
- }
-
- // Reset the strings.
- saveWebpageUrl = "";
- saveWebpageFilePath = "";
- } 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 (saveWebpageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory.
- // Save the webpage according to the save type.
- switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
- // Save the URL.
- new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
- break;
-
- case StoragePermissionDialog.SAVE_ARCHIVE:
- // Save the webpage archive.
- saveWebpageArchive(saveWebpageFilePath);
- break;
-
- case StoragePermissionDialog.SAVE_IMAGE:
- // Save the webpage image.
- new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
- break;
- }
+ String saveWebpageFilePath = fileNameEditText.getText().toString();
- // Reset the strings.
- saveWebpageUrl = "";
- saveWebpageFilePath = "";
- } else { // The file path is 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 = StoragePermissionDialog.displayDialog(saveType);
-
- // 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 write external storage permission according to the save type. The URL will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, saveType);
- }
- }
- }
- }
-
- @Override
- public void onCloseStoragePermissionDialog(int requestType) {
- // Request the write external storage permission according to the request type. The file will be opened when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestType);
-
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included).
- if (grantResults.length > 0) {
- switch (requestCode) {
- case StoragePermissionDialog.OPEN:
- // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
- // Load the file.
- currentWebView.loadUrl("file://" + openFilePath);
- } else { // The storage permission was not granted.
- // Display an error snackbar.
- Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
- }
- break;
-
- case StoragePermissionDialog.SAVE_URL:
- // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
- // Save the raw URL.
- new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
- } else { // The storage permission was not granted.
- // Display an error snackbar.
- Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
- }
- break;
-
- case StoragePermissionDialog.SAVE_ARCHIVE:
- // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
- // Save the webpage archive.
- saveWebpageArchive(saveWebpageFilePath);
- } else { // The storage permission was not granted.
- // Display an error snackbar.
- Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
- }
- break;
-
- case StoragePermissionDialog.SAVE_IMAGE:
- // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
- // Save the webpage image.
- new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
- } else { // The storage permission was not granted.
- // Display an error snackbar.
- Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
- }
- break;
- }
+ //Save the webpage according to the save type.
+ switch (saveType) {
+ case SaveWebpageDialog.SAVE_URL:
+ // Save the URL.
+ new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
+ break;
- // Reset the strings.
- openFilePath = "";
- saveWebpageUrl = "";
- saveWebpageFilePath = "";
+ case SaveWebpageDialog.SAVE_IMAGE:
+ // Save the webpage image.
+ new SaveWebpageImage(this, saveWebpageFilePath, currentWebView).execute();
+ break;
}
}
PackageManager packageManager = getPackageManager();
// Check to see if I2P is in the list. This will throw an error and drop to the catch section if it isn't installed.
- packageManager.getPackageInfo("org.torproject.android", 0);
+ packageManager.getPackageInfo("net.i2p.android.router", 0);
} catch (PackageManager.NameNotFoundException exception) { // I2P is not installed.
// Sow the I2P not installed dialog if it is not already displayed.
if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
}
}
- private void saveWebpageArchive(String filePath) {
- // Save the webpage archive.
- currentWebView.saveWebArchive(filePath);
-
- // Display a snackbar.
- Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + filePath, Snackbar.LENGTH_SHORT);
-
- // Add an open option to the snackbar.
- saveWebpageArchiveSnackbar.setAction(R.string.open, (View view) -> {
- // Get a file for the file name string.
- File file = new File(filePath);
-
- // Declare a file URI variable.
- Uri fileUri;
-
- // Get the URI for the file according to the Android version.
- if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
- fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file);
- } else { // Get the raw file path URI.
- fileUri = Uri.fromFile(file);
- }
-
- // Get a handle for the content resolver.
- ContentResolver contentResolver = getContentResolver();
-
- // Create an open intent with `ACTION_VIEW`.
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
-
- // Set the URI and the MIME type.
- openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
-
- // Allow the app to read the file URI.
- openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Show the chooser.
- startActivity(Intent.createChooser(openIntent, getString(R.string.open)));
- });
-
- // Show the snackbar.
- saveWebpageArchiveSnackbar.show();
- }
-
private void clearAndExit() {
// Get a handle for the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
}
// Instantiate the save dialog.
- DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+ DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
nestedScrollWebView.getAcceptFirstPartyCookies());
// Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
/*
- * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2019,2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
// Checked for pinned mismatches if there is pinned information and it is not ignored.
if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && !nestedScrollWebView.ignorePinnedDomainInformation()) {
- CheckPinnedMismatchHelper.checkPinnedMismatch(fragmentManager, nestedScrollWebView);
+ CheckPinnedMismatchHelper.checkPinnedMismatch(activity, fragmentManager, nestedScrollWebView);
}
}
}
\ No newline at end of file
/*
- * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2020-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
public class GetLogcat extends AsyncTask<Void, Void, String> {
// Define the class variables.
private final WeakReference<Activity> activityWeakReference;
- private int scrollViewYPositionInt;
+ private final int scrollViewYPositionInt;
// The public constructor.
public GetLogcat(Activity activity, int scrollViewYPositionInt) {
/*
- * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2020-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
public class GetUrlSize extends AsyncTask<String, Void, String> {
// Define weak references for the calling context and alert dialog.
- private WeakReference<Context> contextWeakReference;
- private WeakReference<AlertDialog> alertDialogWeakReference;
+ private final WeakReference<Context> contextWeakReference;
+ private final WeakReference<AlertDialog> alertDialogWeakReference;
// Define the class variables.
- private String userAgent;
- private boolean cookiesEnabled;
+ private final String userAgent;
+ private final boolean cookiesEnabled;
// The public constructor.
public GetUrlSize(Context context, AlertDialog alertDialog, String userAgent, boolean cookiesEnabled) {
/*
- * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2019,2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
}
// Define a populate blocklists listener.
- private PopulateBlocklistsListener populateBlocklistsListener;
+ private final PopulateBlocklistsListener populateBlocklistsListener;
// Define weak references for the activity and context.
- private WeakReference<Context> contextWeakReference;
- private WeakReference<Activity> activityWeakReference;
+ private final WeakReference<Context> contextWeakReference;
+ private final WeakReference<Activity> activityWeakReference;
// The public constructor.
public PopulateBlocklists(Context context, Activity activity) {
/*
- * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2020-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
public class PrepareSaveDialog extends AsyncTask<String, Void, String[]> {
// Define weak references.
- private WeakReference<Activity> activityWeakReference;
- private WeakReference<Context> contextWeakReference;
- private WeakReference<FragmentManager> fragmentManagerWeakReference;
+ private final WeakReference<Activity> activityWeakReference;
+ private final WeakReference<Context> contextWeakReference;
+ private final WeakReference<FragmentManager> fragmentManagerWeakReference;
// Define the class variables.
- private int saveType;
- private String userAgent;
- private boolean cookiesEnabled;
+ private final int saveType;
+ private final String userAgent;
+ private final boolean cookiesEnabled;
private String urlString;
// The public constructor.
return;
}
+ // Prevent the dialog from displaying if the app window is not visible.
+ // The asynctask continues to function even when the app is paused. Attempting to display a dialog in that state leads to a crash.
+ while (!activity.getWindow().isActive()) {
+ try {
+ // The window is not active. Wait 1 second.
+ wait(1000);
+ } catch (InterruptedException e) {
+ // Do nothing.
+ }
+ }
+
// Instantiate the save dialog.
DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(saveType, urlString, fileStringArray[0], fileStringArray[1], userAgent, cookiesEnabled);
/*
- * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2020-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.asynctasks;
import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
-import android.view.View;
import android.widget.LinearLayout;
-import androidx.core.content.FileProvider;
-
import com.google.android.material.snackbar.Snackbar;
import com.stoutner.privacybrowser.R;
import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
+import java.io.OutputStream;
import java.lang.ref.WeakReference;
public class SaveAboutVersionImage extends AsyncTask<Void, Void, String> {
// Declare the weak references.
- private WeakReference<Context> contextWeakReference;
- private WeakReference<Activity> activityWeakReference;
- private WeakReference<LinearLayout> aboutVersionLinearLayoutWeakReference;
+ private final WeakReference<Activity> activityWeakReference;
+ private final WeakReference<LinearLayout> aboutVersionLinearLayoutWeakReference;
// Declare the class constants.
private final String SUCCESS = "Success";
// Declare the class variables.
private Snackbar savingImageSnackbar;
private Bitmap aboutVersionBitmap;
- private String filePathString;
+ private final String fileNameString;
// The public constructor.
- public SaveAboutVersionImage(Context context, Activity activity, String filePathString, LinearLayout aboutVersionLinearLayout) {
+ public SaveAboutVersionImage(Activity activity, String fileNameString, LinearLayout aboutVersionLinearLayout) {
// Populate the weak references.
- contextWeakReference = new WeakReference<>(context);
activityWeakReference = new WeakReference<>(activity);
aboutVersionLinearLayoutWeakReference = new WeakReference<>(aboutVersionLinearLayout);
// Store the class variables.
- this.filePathString = filePathString;
+ this.fileNameString = fileNameString;
}
// `onPreExecute()` operates on the UI thread.
}
// Create a saving image snackbar.
- savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image) + " " + filePathString, Snackbar.LENGTH_INDEFINITE);
+ savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image) + " " + fileNameString, Snackbar.LENGTH_INDEFINITE);
// Display the saving image snackbar.
savingImageSnackbar.show();
// Convert the bitmap to a PNG. `0` is for lossless compression (the only option for a PNG). This compression takes a long time. Once the minimum API >= 30 this could be replaced with WEBP_LOSSLESS.
aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream);
- // Get a file for the image.
- File imageFile = new File(filePathString);
-
- // Delete the current file if it exists.
- if (imageFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- imageFile.delete();
- }
-
// Create a file creation disposition string.
String fileCreationDisposition = SUCCESS;
try {
- // Create an image file output stream.
- FileOutputStream imageFileOutputStream = new FileOutputStream(imageFile);
+ // Open an output stream.
+ OutputStream outputStream = activity.getContentResolver().openOutputStream(Uri.parse(fileNameString));
// Write the webpage image to the image file.
- aboutVersionByteArrayOutputStream.writeTo(imageFileOutputStream);
+ aboutVersionByteArrayOutputStream.writeTo(outputStream);
- // Create a media scanner intent, which adds items like pictures to Android's recent file list.
- Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ // Flush the output stream.
+ outputStream.flush();
- // Add the URI to the media scanner intent.
- mediaScannerIntent.setData(Uri.fromFile(imageFile));
-
- // Make it so.
- activity.sendBroadcast(mediaScannerIntent);
+ // Close the output stream.
+ outputStream.close();
} catch (Exception exception) {
// Store the error in the file creation disposition string.
fileCreationDisposition = exception.toString();
@Override
protected void onPostExecute(String fileCreationDisposition) {
// Get handles for the weak references.
- Context context = contextWeakReference.get();
Activity activity = activityWeakReference.get();
LinearLayout aboutVersionLinearLayout = aboutVersionLinearLayoutWeakReference.get();
// Display a file creation disposition snackbar.
if (fileCreationDisposition.equals(SUCCESS)) {
// Create a file saved snackbar.
- Snackbar imageSavedSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.file_saved) + " " + filePathString, Snackbar.LENGTH_SHORT);
-
- // Add an open action.
- imageSavedSnackbar.setAction(R.string.open, (View view) -> {
- // Get a file for the file path string.
- File file = new File(filePathString);
-
- // Declare a file URI variable.
- Uri fileUri;
-
- // Get the URI for the file according to the Android version.
- if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
- fileUri = FileProvider.getUriForFile(context, activity.getString(R.string.file_provider), file);
- } else { // Get the raw file path URI.
- fileUri = Uri.fromFile(file);
- }
-
- // Get a handle for the content resolver.
- ContentResolver contentResolver = context.getContentResolver();
-
- // Create an open intent with `ACTION_VIEW`.
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
-
- // Autodetect the MIME type.
- openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
-
- // Allow the app to read the file URI.
- openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Show the chooser.
- activity.startActivity(Intent.createChooser(openIntent, context.getString(R.string.open)));
- });
-
- // Show the image saved snackbar.
- imageSavedSnackbar.show();
+ Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show();
} else {
Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file) + " " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();
}
/*
- * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2020-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.asynctasks;
import android.app.Activity;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.util.Base64;
-import android.view.View;
import android.webkit.CookieManager;
-import android.webkit.MimeTypeMap;
-
-import androidx.core.content.FileProvider;
import com.google.android.material.snackbar.Snackbar;
import com.stoutner.privacybrowser.R;
import com.stoutner.privacybrowser.views.NoSwipeViewPager;
import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
String saveDisposition = SUCCESS;
try {
- // Get the file.
- File file = new File(filePathString);
-
- // Delete the file if it exists.
- if (file.exists()) {
- //noinspection ResultOfMethodCallIgnored
- file.delete();
- }
-
- // Create a new file.
- //noinspection ResultOfMethodCallIgnored
- file.createNewFile();
-
- // Create an output file stream.
- OutputStream fileOutputStream = new FileOutputStream(file);
+ // Open an output stream.
+ OutputStream outputStream = activity.getContentResolver().openOutputStream(Uri.parse(filePathString));
// Save the URL.
if (urlToSave[0].startsWith("data:")) { // The URL contains the entire data of an image.
// Decode the Base64 string to a byte array.
byte[] base64DecodedDataByteArray = Base64.decode(base64DataString, Base64.DEFAULT);
- // Write the Base64 byte array to the file output stream.
- fileOutputStream.write(base64DecodedDataByteArray);
-
- // Close the file output stream.
- fileOutputStream.close();
+ // Write the Base64 byte array to the output stream.
+ outputStream.write(base64DecodedDataByteArray);
} else { // The URL points to the data location on the internet.
// Get the URL from the calling activity.
URL url = new URL(urlToSave[0]);
// Attempt to read data from the input stream and store it in the output stream. Also store the amount of data read in the buffer length variable.
while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer in > 0.
// Write the contents of the conversion buffer to the file output stream.
- fileOutputStream.write(conversionBufferByteArray, 0, bufferLength);
+ outputStream.write(conversionBufferByteArray, 0, bufferLength);
// Update the file download progress snackbar.
if (fileSize == -1) { // The file size is unknown.
// Close the input stream.
inputStream.close();
-
- // Close the file output stream.
- fileOutputStream.close();
} finally {
// Disconnect the HTTP URL connection.
httpUrlConnection.disconnect();
}
}
- // Create a media scanner intent, which adds items like pictures to Android's recent file list.
- Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ // Flush the output stream.
+ outputStream.flush();
- // Add the URI to the media scanner intent.
- mediaScannerIntent.setData(Uri.fromFile(file));
-
- // Make it so.
- activity.sendBroadcast(mediaScannerIntent);
+ // Close the output stream.
+ outputStream.close();
} catch (Exception exception) {
// Store the error in the save disposition string.
saveDisposition = exception.toString();
@Override
protected void onPostExecute(String saveDisposition) {
// Get handles for the context and activity.
- Context context = contextWeakReference.get();
Activity activity = activityWeakReference.get();
// Abort if the activity is gone.
// Display a save disposition snackbar.
if (saveDisposition.equals(SUCCESS)) {
- // Create a file saved snackbar.
- Snackbar fileSavedSnackbar = Snackbar.make(noSwipeViewPager, activity.getString(R.string.file_saved) + " " + filePathString, Snackbar.LENGTH_LONG);
-
- // Add an open action if the file is not an APK on API >= 26 (that scenario requires the REQUEST_INSTALL_PACKAGES permission).
- if (!(Build.VERSION.SDK_INT >= 26 && filePathString.endsWith(".apk"))) {
- fileSavedSnackbar.setAction(R.string.open, (View view) -> {
- // Get a file for the file path string.
- File file = new File(filePathString);
-
- // Declare a file URI variable.
- Uri fileUri;
-
- // Get the URI for the file according to the Android version.
- if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
- fileUri = FileProvider.getUriForFile(context, activity.getString(R.string.file_provider), file);
- } else { // Get the raw file path URI.
- fileUri = Uri.fromFile(file);
- }
-
- // Get a handle for the content resolver.
- ContentResolver contentResolver = context.getContentResolver();
-
- // Create an open intent with `ACTION_VIEW`.
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
-
- // Set the URI and the MIME type.
- if (filePathString.endsWith("apk") || filePathString.endsWith("APK")) { // Force detection of APKs.
- openIntent.setDataAndType(fileUri, MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"));
- } else { // Autodetect the MIME type.
- openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
- }
-
- // Allow the app to read the file URI.
- openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Show the chooser.
- activity.startActivity(Intent.createChooser(openIntent, context.getString(R.string.open)));
- });
- }
-
- // Show the file saved snackbar.
- fileSavedSnackbar.show();
+ // Display the file saved snackbar.
+ Snackbar.make(noSwipeViewPager, activity.getString(R.string.file_saved) + " " + filePathString, Snackbar.LENGTH_LONG).show();
} else {
// Display the file saving error.
Snackbar.make(noSwipeViewPager, activity.getString(R.string.error_saving_file) + " " + saveDisposition, Snackbar.LENGTH_INDEFINITE).show();
/*
- * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.asynctasks;
import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
-import android.view.View;
-
-import androidx.core.content.FileProvider;
import com.google.android.material.snackbar.Snackbar;
import com.stoutner.privacybrowser.views.NestedScrollWebView;
import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
+import java.io.OutputStream;
import java.lang.ref.WeakReference;
public class SaveWebpageImage extends AsyncTask<Void, Void, String> {
// Declare the weak references.
- private WeakReference<Context> contextWeakReference;
- private WeakReference<Activity> activityWeakReference;
- private WeakReference<NestedScrollWebView> nestedScrollWebViewWeakReference;
+ private final WeakReference<Activity> activityWeakReference;
+ private final WeakReference<NestedScrollWebView> nestedScrollWebViewWeakReference;
// Declare the class constants.
private final String SUCCESS = "Success";
// Declare the class variables.
private Snackbar savingImageSnackbar;
private Bitmap webpageBitmap;
- private String filePathString;
+ private final String filePathString;
// The public constructor.
- public SaveWebpageImage(Context context, Activity activity, String filePathString, NestedScrollWebView nestedScrollWebView) {
+ public SaveWebpageImage(Activity activity, String filePathString, NestedScrollWebView nestedScrollWebView) {
// Populate the weak references.
- contextWeakReference = new WeakReference<>(context);
activityWeakReference = new WeakReference<>(activity);
nestedScrollWebViewWeakReference = new WeakReference<>(nestedScrollWebView);
// Convert the bitmap to a PNG. `0` is for lossless compression (the only option for a PNG). This compression takes a long time. Once the minimum API >= 30 this could be replaced with WEBP_LOSSLESS.
webpageBitmap.compress(Bitmap.CompressFormat.PNG, 0, webpageByteArrayOutputStream);
- // Get a file for the image.
- File imageFile = new File(filePathString);
-
- // Delete the current file if it exists.
- if (imageFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- imageFile.delete();
- }
-
// Create a file creation disposition string.
String fileCreationDisposition = SUCCESS;
try {
// Create an image file output stream.
- FileOutputStream imageFileOutputStream = new FileOutputStream(imageFile);
+ OutputStream imageFileOutputStream = activity.getContentResolver().openOutputStream(Uri.parse(filePathString));
// Write the webpage image to the image file.
webpageByteArrayOutputStream.writeTo(imageFileOutputStream);
-
- // Create a media scanner intent, which adds items like pictures to Android's recent file list.
- Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
-
- // Add the URI to the media scanner intent.
- mediaScannerIntent.setData(Uri.fromFile(imageFile));
-
- // Make it so.
- activity.sendBroadcast(mediaScannerIntent);
} catch (Exception exception) {
// Store the error in the file creation disposition string.
fileCreationDisposition = exception.toString();
@Override
protected void onPostExecute(String fileCreationDisposition) {
// Get handles for the weak references.
- Context context = contextWeakReference.get();
Activity activity = activityWeakReference.get();
NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
// Display a file creation disposition snackbar.
if (fileCreationDisposition.equals(SUCCESS)) {
- // Create a file saved snackbar.
- Snackbar imageSavedSnackbar = Snackbar.make(nestedScrollWebView, activity.getString(R.string.file_saved) + " " + filePathString, Snackbar.LENGTH_SHORT);
-
- // Add an open action.
- imageSavedSnackbar.setAction(R.string.open, (View view) -> {
- // Get a file for the file path string.
- File file = new File(filePathString);
-
- // Declare a file URI variable.
- Uri fileUri;
-
- // Get the URI for the file according to the Android version.
- if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
- fileUri = FileProvider.getUriForFile(context, activity.getString(R.string.file_provider), file);
- } else { // Get the raw file path URI.
- fileUri = Uri.fromFile(file);
- }
-
- // Get a handle for the content resolver.
- ContentResolver contentResolver = context.getContentResolver();
-
- // Create an open intent with `ACTION_VIEW`.
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
-
- // Autodetect the MIME type.
- openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
-
- // Allow the app to read the file URI.
- openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Show the chooser.
- activity.startActivity(Intent.createChooser(openIntent, context.getString(R.string.open)));
- });
-
- // Show the image saved snackbar.
- imageSavedSnackbar.show();
+ // Display the file saved snackbar.
+ Snackbar.make(nestedScrollWebView, activity.getString(R.string.file_saved) + " " + filePathString, Snackbar.LENGTH_SHORT).show();
} else {
// Display the file saving error.
Snackbar.make(nestedScrollWebView, activity.getString(R.string.error_saving_file) + " " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();
package com.stoutner.privacybrowser.dialogs
-import android.Manifest
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
-import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Bundle
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 androidx.appcompat.app.AlertDialog
-import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
import com.stoutner.privacybrowser.R
import com.stoutner.privacybrowser.activities.MainWebViewActivity
-import com.stoutner.privacybrowser.helpers.DownloadLocationHelper
-
-import java.io.File
class OpenDialog : DialogFragment() {
// Define the open listener.
// Get handles for the layout items.
val fileNameEditText = alertDialog.findViewById<EditText>(R.id.file_name_edittext)!!
val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
- val fileDoesNotExistTextView = alertDialog.findViewById<TextView>(R.id.file_does_not_exist_textview)!!
- val storagePermissionTextView = alertDialog.findViewById<TextView>(R.id.storage_permission_textview)!!
val openButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+ // Initially disable the open button.
+ openButton.isEnabled = false
+
// Update the status of the open button when the file name changes.
fileNameEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
// Get the current file name.
val fileNameString = fileNameEditText.text.toString()
- // Convert the file name string to a file.
- val file = File(fileNameString)
-
- // Check to see if the file exists.
- if (file.exists()) { // The file exists.
- // Hide the notification that the file does not exist.
- fileDoesNotExistTextView.visibility = View.GONE
-
- // Enable the open button.
- openButton.isEnabled = true
- } else { // The file does not exist.
- // Show the notification that the file does not exist.
- fileDoesNotExistTextView.visibility = View.VISIBLE
-
- // Disable the open button.
- openButton.isEnabled = false
- }
+ // Enable the open button if the file name is populated.
+ openButton.isEnabled = fileNameString.isNotEmpty()
}
})
- // Instantiate the download location helper.
- val downloadLocationHelper = DownloadLocationHelper()
-
- // Get the default file path.
- val defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/"
-
- // Display the default file path.
- fileNameEditText.setText(defaultFilePath)
-
- // Move the cursor to the end of the default file path.
- fileNameEditText.setSelection(defaultFilePath.length)
-
- // Hide the storage permission text view if the permission has already been granted.
- if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
- storagePermissionTextView.visibility = View.GONE
- }
-
// Handle clicks on the browse button.
browseButton.setOnClickListener {
// Create the file picker intent.
val browseIntent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ // Only display files that can be opened.
+ browseIntent.addCategory(Intent.CATEGORY_OPENABLE)
+
// Set the intent MIME type to include all files so that everything is visible.
browseIntent.type = "*/*"
package com.stoutner.privacybrowser.dialogs
-import android.Manifest
import android.annotation.SuppressLint
-import android.os.Bundle
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
-import android.content.pm.PackageManager
import android.content.res.Configuration
-import android.view.View
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
import android.view.WindowManager
import android.widget.Button
import android.widget.EditText
-import android.widget.TextView
-import android.text.TextWatcher
-import android.text.Editable
-
import androidx.appcompat.app.AlertDialog
-import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
-
import com.stoutner.privacybrowser.R
-import com.stoutner.privacybrowser.helpers.DownloadLocationHelper
-
-import java.io.File
// Declare the class constants.
private const val SAVE_TYPE = "save_type"
// Get handles for the layout items.
val fileNameEditText = alertDialog.findViewById<EditText>(R.id.file_name_edittext)!!
val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
- val fileExistsWarningTextView = alertDialog.findViewById<TextView>(R.id.file_exists_warning_textview)!!
- val storagePermissionTextView = alertDialog.findViewById<TextView>(R.id.storage_permission_textview)!!
val saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+ // Initially disable the save button.
+ saveButton.isEnabled = false
+
// Update the status of the save button when the file name changes.
fileNameEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// Get the current file name.
val fileNameString = fileNameEditText.text.toString()
- // Convert the file name string to a file.
- val file = File(fileNameString)
-
- // Check to see if the file exists.
- if (file.exists()) {
- // Show the file exists warning.
- fileExistsWarningTextView.visibility = View.VISIBLE
- } else {
- // Hide the file exists warning.
- fileExistsWarningTextView.visibility = View.GONE
- }
-
// Enable the save button if the file name is populated.
saveButton.isEnabled = fileNameString.isNotEmpty()
}
SAVE_ABOUT_VERSION_IMAGE -> fileName = getString(R.string.privacy_browser_version_png)
}
- // Instantiate the download location helper.
- val downloadLocationHelper = DownloadLocationHelper()
-
- // Get the default file path.
- val defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/" + fileName
-
- // Display the default file path.
- fileNameEditText.setText(defaultFilePath)
-
- // Hide the storage permission text view if the permission has already been granted.
- if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
- storagePermissionTextView.visibility = View.GONE
- }
-
// Handle clicks on the browse button.
browseButton.setOnClickListener {
// Create the file picker intent.
// 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, but it must be run under `activity` so the request code is correct.
+ // Launch the file picker. There is only one `startActivityForResult()`, so the request code is simply set to 0, but it must be run under `activity` so the response is processed correctly.
requireActivity().startActivityForResult(browseIntent, 0)
}
/*
- * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.dialogs;
-import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
-import android.provider.DocumentsContract;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import com.stoutner.privacybrowser.R;
import com.stoutner.privacybrowser.activities.MainWebViewActivity;
import com.stoutner.privacybrowser.asynctasks.GetUrlSize;
-import com.stoutner.privacybrowser.helpers.DownloadLocationHelper;
-
-import java.io.File;
public class SaveWebpageDialog extends DialogFragment {
+ public static final int SAVE_URL = 0;
+ public static final int SAVE_IMAGE = 1;
+
// Define the save webpage listener.
private SaveWebpageListener saveWebpageListener;
}
// Define the get URL size AsyncTask. This allows previous instances of the task to be cancelled if a new one is run.
+ @SuppressWarnings("rawtypes")
private AsyncTask getUrlSize;
@Override
// Get the arguments from the bundle.
int saveType = arguments.getInt("save_type");
- String urlString = arguments.getString("url_string");
+ String originalUrlString = arguments.getString("url_string");
String fileSizeString = arguments.getString("file_size_string");
String contentDispositionFileNameString = arguments.getString("content_disposition_file_name_string");
String userAgentString = arguments.getString("user_agent_string");
boolean cookiesEnabled = arguments.getBoolean("cookies_enabled");
- // Get a handle for the activity and the context.
- Activity activity = requireActivity();
+ // Get handles for the context and the activity.
Context context = requireContext();
+ Activity activity = requireActivity();
// Use an alert dialog builder to create the alert dialog.
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialog);
// Get the current theme status.
int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
- // Set the icon according to the theme.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { // The night theme is enabled.
- // Set the icon according to the save type.
- switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
- dialogBuilder.setIcon(R.drawable.copy_enabled_night);
- break;
-
- case StoragePermissionDialog.SAVE_ARCHIVE:
- dialogBuilder.setIcon(R.drawable.dom_storage_cleared_night);
- break;
+ // Set the title and icon according to the save type.
+ switch (saveType) {
+ case SAVE_URL:
+ // Set the title.
+ dialogBuilder.setTitle(R.string.save_url);
- case StoragePermissionDialog.SAVE_IMAGE:
- dialogBuilder.setIcon(R.drawable.images_enabled_night);
- break;
- }
- } else { // The day theme is enabled.
- // Set the icon according to the save type.
- switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
+ // Set the icon according to the theme.
+ if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
dialogBuilder.setIcon(R.drawable.copy_enabled_day);
- break;
+ } else {
+ dialogBuilder.setIcon(R.drawable.copy_enabled_night);
+ }
+ break;
- case StoragePermissionDialog.SAVE_ARCHIVE:
- dialogBuilder.setIcon(R.drawable.dom_storage_cleared_day);
- break;
+ case SAVE_IMAGE:
+ // Set the title.
+ dialogBuilder.setTitle(R.string.save_image);
- case StoragePermissionDialog.SAVE_IMAGE:
+ // Set the icon according to the theme.
+ if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
dialogBuilder.setIcon(R.drawable.images_enabled_day);
- break;
- }
- }
-
- // Set the title according to the type.
- switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
- dialogBuilder.setTitle(R.string.save);
- break;
-
- case StoragePermissionDialog.SAVE_ARCHIVE:
- dialogBuilder.setTitle(R.string.save_archive);
- break;
+ } else {
- case StoragePermissionDialog.SAVE_IMAGE:
- dialogBuilder.setTitle(R.string.save_image);
+ dialogBuilder.setIcon(R.drawable.images_enabled_night);
+ }
break;
}
// Set the view. The parent view is null because it will be assigned by the alert dialog.
- dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_url_dialog, null));
+ dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_webpage_dialog, null));
// Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else.
dialogBuilder.setNegativeButton(R.string.cancel, null);
// Set the save button listener.
dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> {
// Return the dialog fragment to the parent activity.
- saveWebpageListener.onSaveWebpage(saveType, urlString, this);
+ saveWebpageListener.onSaveWebpage(saveType, originalUrlString, this);
});
// Create an alert dialog from the builder.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
// Get the screenshot preference.
- boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
+ boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
// Disable screenshots if not allowed.
if (!allowScreenshots) {
EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext);
Button browseButton = alertDialog.findViewById(R.id.browse_button);
TextView fileSizeTextView = alertDialog.findViewById(R.id.file_size_textview);
- TextView fileExistsWarningTextView = alertDialog.findViewById(R.id.file_exists_warning_textview);
- TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview);
Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
// Remove the incorrect warnings that the views might be null.
assert fileNameEditText != null;
assert browseButton != null;
assert fileSizeTextView != null;
- assert fileExistsWarningTextView != null;
- assert storagePermissionTextView != null;
// Set the file size text view.
fileSizeTextView.setText(fileSizeString);
// Modify the layout based on the save type.
- if (saveType == StoragePermissionDialog.SAVE_URL) { // A URL is being saved.
+ if (saveType == SAVE_URL) { // A URL is being saved.
// Remove the incorrect lint error below that the URL string might be null.
- assert urlString != null;
+ assert originalUrlString != null;
// Populate the URL edit text according to the type. This must be done before the text change listener is created below so that the file size isn't requested again.
- if (urlString.startsWith("data:")) { // The URL contains the entire data of an image.
+ if (originalUrlString.startsWith("data:")) { // The URL contains the entire data of an image.
// Get a substring of the data URL with the first 100 characters. Otherwise, the user interface will freeze while trying to layout the edit text.
- String urlSubstring = urlString.substring(0, 100) + "…";
+ String urlSubstring = originalUrlString.substring(0, 100) + "…";
// Populate the URL edit text with the truncated URL.
urlEditText.setText(urlSubstring);
urlEditText.setInputType(InputType.TYPE_NULL);
} else { // The URL contains a reference to the location of the data.
// Populate the URL edit text with the full URL.
- urlEditText.setText(urlString);
+ urlEditText.setText(originalUrlString);
}
// Update the file size and the status of the save button when the URL changes.
fileSizeTextView.setVisibility(View.GONE);
}
+ // Initially disable the save button.
+ saveButton.setEnabled(false);
+
// Update the status of the save button when the file name changes.
fileNameEditText.addTextChangedListener(new TextWatcher() {
@Override
// Get the current file name.
String fileNameString = fileNameEditText.getText().toString();
- // Convert the file name string to a file.
- File file = new File(fileNameString);
-
- // Check to see if the file exists.
- if (file.exists()) {
- // Show the file exists warning.
- fileExistsWarningTextView.setVisibility(View.VISIBLE);
- } else {
- // Hide the file exists warning.
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
-
// Enable the save button based on the save type.
- if (saveType == StoragePermissionDialog.SAVE_URL) { // A URL is being saved.
+ if (saveType == SAVE_URL) { // A URL is being saved.
// Enable the save button if the file name and the URL is populated.
saveButton.setEnabled(!fileNameString.isEmpty() && !urlEditText.getText().toString().isEmpty());
} else { // An archive or an image is being saved.
// Set the file name according to the type.
switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
+ case SAVE_URL:
// Use the file name from the content disposition.
fileName = contentDispositionFileNameString;
break;
- case StoragePermissionDialog.SAVE_ARCHIVE:
- // Use an archive name ending in `.mht`.
- fileName = getString(R.string.webpage_mht);
- break;
-
- case StoragePermissionDialog.SAVE_IMAGE:
+ case SAVE_IMAGE:
// Use a file name ending in `.png`.
fileName = getString(R.string.webpage_png);
break;
// Save the file name as the default file name. This must be final to be used in the lambda below.
final String defaultFileName = fileName;
- // Instantiate the download location helper.
- DownloadLocationHelper downloadLocationHelper = new DownloadLocationHelper();
-
- // Get the default file path.
- String defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/" + defaultFileName;
-
- // Populate the file name edit text.
- fileNameEditText.setText(defaultFilePath);
-
- // Move the cursor to the end of the default file path.
- fileNameEditText.setSelection(defaultFilePath.length());
-
// Handle clicks on the browse button.
browseButton.setOnClickListener((View view) -> {
// Create the file picker intent.
// Set the initial file name according to the type.
browseIntent.putExtra(Intent.EXTRA_TITLE, defaultFileName);
- // 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);
activity.startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_SAVE_WEBPAGE_REQUEST_CODE);
});
- // Hide the storage permission text view if the permission has already been granted.
- if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
- storagePermissionTextView.setVisibility(View.GONE);
- }
-
// Return the alert dialog.
return alertDialog;
}
+++ /dev/null
-/*
- * Copyright © 2018-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dialogs;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.view.WindowManager;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-
-import com.stoutner.privacybrowser.R;
-
-public class StoragePermissionDialog extends DialogFragment {
- // Define the save type constants.
- public static final int OPEN = 0;
- public static final int SAVE_URL = 1;
- public static final int SAVE_ARCHIVE = 2;
- public static final int SAVE_TEXT = 3;
- public static final int SAVE_IMAGE = 4;
-
- // 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(int requestType);
- }
-
- @Override
- public void onAttach(@NonNull Context context) {
- // Run the default commands.
- super.onAttach(context);
-
- // Get a handle for the listener from the launching context.
- storagePermissionDialogListener = (StoragePermissionDialogListener) context;
- }
-
- public static StoragePermissionDialog displayDialog(int requestType) {
- // Create an arguments bundle.
- Bundle argumentsBundle = new Bundle();
-
- // Store the save type in the bundle.
- argumentsBundle.putInt("request_type", requestType);
-
- // Create a new instance of the storage permission dialog.
- StoragePermissionDialog storagePermissionDialog = new StoragePermissionDialog();
-
- // Add the arguments bundle to the new dialog.
- storagePermissionDialog.setArguments(argumentsBundle);
-
- // Return the new dialog.
- return storagePermissionDialog;
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- // Get a handle for the arguments.
- Bundle arguments = getArguments();
-
- // Remove the incorrect lint warning that the arguments might be null.
- assert arguments != null;
-
- // Get the save type.
- int requestType = arguments.getInt("request_type");
-
- // Use a builder to create the alert dialog.
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog);
-
- // Get the current theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
- // Set the icon according to the theme.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
- dialogBuilder.setIcon(R.drawable.import_export_night);
- } else {
- dialogBuilder.setIcon(R.drawable.import_export_day);
- }
-
- // Set the title.
- dialogBuilder.setTitle(R.string.storage_permission);
-
- // Set the text.
- dialogBuilder.setMessage(R.string.storage_permission_message);
-
- // Set an listener on the OK button.
- dialogBuilder.setNegativeButton(R.string.ok, (DialogInterface dialog, int which) -> {
- // Inform the parent activity that the dialog was closed.
- storagePermissionDialogListener.onCloseStoragePermissionDialog(requestType);
- });
-
- // Create an alert dialog from the builder.
- final AlertDialog alertDialog = dialogBuilder.create();
-
- // Get a handle for the shared preferences.
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
-
- // Get the screenshot preference.
- boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
-
- // Disable screenshots if not allowed.
- if (!allowScreenshots) {
- // Remove the warning below that `getWindow()` might be null.
- assert alertDialog.getWindow() != null;
-
- // Disable screenshots.
- alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
- }
-
- // Return the alert dialog.
- return alertDialog;
- }
-}
\ No newline at end of file
/*
- * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.WebView;
import com.stoutner.privacybrowser.R;
import com.stoutner.privacybrowser.activities.MainWebViewActivity;
-import com.stoutner.privacybrowser.helpers.DownloadLocationHelper;
import com.stoutner.privacybrowser.helpers.ProxyHelper;
public class SettingsFragment extends PreferenceFragmentCompat {
Preference clearLogcatPreference = findPreference(getString(R.string.clear_logcat_key));
Preference clearCachePreference = findPreference("clear_cache");
Preference homepagePreference = findPreference("homepage");
- Preference downloadLocationPreference = findPreference("download_location");
- Preference downloadCustomLocationPreference = findPreference("download_custom_location");
Preference fontSizePreference = findPreference("font_size");
Preference openIntentsInNewTabPreference = findPreference("open_intents_in_new_tab");
Preference swipeToRefreshPreference = findPreference("swipe_to_refresh");
assert clearLogcatPreference != null;
assert clearCachePreference != null;
assert homepagePreference != null;
- assert downloadLocationPreference != null;
- assert downloadCustomLocationPreference != null;
assert fontSizePreference != null;
assert openIntentsInNewTabPreference != null;
assert swipeToRefreshPreference != null;
String userAgentName = savedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
String searchString = savedPreferences.getString("search", getString(R.string.search_default_value));
String proxyString = savedPreferences.getString("proxy", getString(R.string.proxy_default_value));
- String downloadLocationString = savedPreferences.getString("download_location", getString(R.string.download_location_default_value));
// Get booleans that are used in multiple places from the preferences.
boolean javaScriptEnabled = savedPreferences.getBoolean("javascript", false);
homepagePreference.setSummary(savedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
- // Get the download location string arrays.
- String[] downloadLocationEntriesStringArray = resources.getStringArray(R.array.download_location_entries);
- String[] downloadLocationEntryValuesStringArray = resources.getStringArray(R.array.download_location_entry_values);
-
- // Instantiate the download location helper.
- DownloadLocationHelper downloadLocationHelper = new DownloadLocationHelper();
-
- // Check to see if a download custom location is selected.
- if (downloadLocationString.equals(downloadLocationEntryValuesStringArray[3])) { // A download custom location is selected.
- // Set the download location summary text to be `Custom`.
- downloadLocationPreference.setSummary(downloadLocationEntriesStringArray[3]);
- } else { // A custom download location is not selected.
- // Set the download location summary text to be the download location.
- downloadLocationPreference.setSummary(downloadLocationHelper.getDownloadLocation(context));
-
- // Disable the download custom location preference.
- downloadCustomLocationPreference.setEnabled(false);
- }
-
- // Set the summary text for the download custom location (the default is `"`).
- downloadCustomLocationPreference.setSummary(savedPreferences.getString("download_custom_location", getString(R.string.download_custom_location_default_value)));
-
-
// Set the font size as the summary text for the preference.
fontSizePreference.setSummary(savedPreferences.getString("font_size", getString(R.string.font_size_default_value)) + "%");
clearCachePreference.setIcon(R.drawable.cache_warning);
}
- // Set the download custom location icon.
- if (downloadCustomLocationPreference.isEnabled()) {
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
- downloadCustomLocationPreference.setIcon(R.drawable.downloads_enabled_night);
- } else {
- downloadCustomLocationPreference.setIcon(R.drawable.downloads_enabled_day);
- }
- } else {
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
- downloadCustomLocationPreference.setIcon(R.drawable.downloads_ghosted_night);
- } else {
- downloadCustomLocationPreference.setIcon(R.drawable.downloads_ghosted_day);
- }
- }
-
// Set the open intents in new tab preference icon.
if (savedPreferences.getBoolean("open_intents_in_new_tab", true)) {
if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
allowScreenshotsRestartIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Create a handler to restart the activity.
- Handler allowScreenshotsRestartHandler = new Handler();
+ Handler allowScreenshotsRestartHandler = new Handler(Looper.getMainLooper());
// Create a runnable to restart the activity.
Runnable allowScreenshotsRestartRunnable = () -> {
homepagePreference.setSummary(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
break;
- case "download_location":
- // Get the new download location.
- String newDownloadLocationString = sharedPreferences.getString("download_location", getString(R.string.download_location_default_value));
-
- // Check to see if a download custom location is selected.
- if (newDownloadLocationString.equals(downloadLocationEntryValuesStringArray[3])) { // A download custom location is selected.
- // Set the download location summary text to be `Custom`.
- downloadLocationPreference.setSummary(downloadLocationEntriesStringArray[3]);
-
- // Enable the download custom location preference.
- downloadCustomLocationPreference.setEnabled(true);
- } else { // A download custom location is not selected.
- // Set the download location summary text to be the download location.
- downloadLocationPreference.setSummary(downloadLocationHelper.getDownloadLocation(context));
-
- // Disable the download custom location.
- downloadCustomLocationPreference.setEnabled(newDownloadLocationString.equals(downloadLocationEntryValuesStringArray[3]));
- }
-
- // Update the download custom location icon.
- if (downloadCustomLocationPreference.isEnabled()) {
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
- downloadCustomLocationPreference.setIcon(R.drawable.downloads_enabled_night);
- } else {
- downloadCustomLocationPreference.setIcon(R.drawable.downloads_enabled_day);
- }
- } else {
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
- downloadCustomLocationPreference.setIcon(R.drawable.downloads_ghosted_night);
- } else {
- downloadCustomLocationPreference.setIcon(R.drawable.downloads_ghosted_day);
- }
- }
- break;
-
- case "download_custom_location":
- // Set the new download custom location as the summary text for the preference.
- downloadCustomLocationPreference.setSummary(sharedPreferences.getString("download_custom_location", getString(R.string.download_custom_location_default_value)));
- break;
-
case "font_size":
// Update the font size summary text.
fontSizePreference.setSummary(sharedPreferences.getString("font_size", getString(R.string.font_size_default_value)) + "%");
/*
- * Copyright © 2018-2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2018-2019,2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
package com.stoutner.privacybrowser.helpers;
+import android.app.Activity;
import android.net.http.SslCertificate;
import androidx.fragment.app.DialogFragment;
import java.util.Date;
public class CheckPinnedMismatchHelper {
- public static void checkPinnedMismatch(FragmentManager fragmentManager, NestedScrollWebView nestedScrollWebView) {
+ public static void checkPinnedMismatch(Activity activity, FragmentManager fragmentManager, NestedScrollWebView nestedScrollWebView) {
// Initialize the current SSL certificate variables.
String currentWebsiteIssuedToCName = "";
String currentWebsiteIssuedToOName = "";
!currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
!currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
+ // Prevent the dialog from displaying if the app window is not visible.
+ // The check pinned mismatch helper continues to function even when the app is paused. Attempting to display a dialog in that state leads to a crash.
+ while (!activity.getWindow().isActive()) {
+ try {
+ // The window is not active. Wait 1 second.
+ activity.wait(1000);
+ } catch (InterruptedException e) {
+ // Do nothing.
+ }
+ }
+
// Get a handle for the pinned mismatch alert dialog.
DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(nestedScrollWebView.getWebViewFragmentId());
+++ /dev/null
-/*
- * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.helpers;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.os.Environment;
-
-import androidx.core.content.ContextCompat;
-import androidx.preference.PreferenceManager;
-
-import com.stoutner.privacybrowser.R;
-
-import java.io.File;
-
-public class DownloadLocationHelper {
- public String getDownloadLocation(Context context) {
- // Get the download location entry values string array.
- String[] downloadLocationEntryValuesStringArray = context.getResources().getStringArray(R.array.download_location_entry_values);
-
- // Get the two standard download directories.
- File publicDownloadDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- File publicAppFilesDirectory = context.getExternalFilesDir(null);
-
- // Remove the incorrect lint warning below that the public app files directory might be null.
- assert publicAppFilesDirectory != null;
-
- // Convert the download directories to strings.
- String publicDownloadDirectoryString = publicDownloadDirectory.toString();
- String publicAppFilesDirectoryString = publicAppFilesDirectory.toString();
-
- // Get a handle for the shared preferences.
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-
- // Get the download location strings from the preferences.
- String downloadLocationString = sharedPreferences.getString("download_location", context.getString(R.string.download_location_default_value));
- String downloadCustomLocationString = sharedPreferences.getString("download_custom_location", context.getString(R.string.download_custom_location_default_value));
-
- // Define a string for the default file path.
- String defaultFilePath;
-
- // Set the default file path according to the download location.
- if (downloadLocationString.equals(downloadLocationEntryValuesStringArray[0])) { // the download location is set to auto.
- // Set the download location summary text according to the storage permission status.
- if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
- // Use the public download directory.
- defaultFilePath = publicDownloadDirectoryString;
- } else { // The storage permission has not been granted.
- // Use the public app files directory.
- defaultFilePath = publicAppFilesDirectoryString;
- }
- } else if (downloadLocationString.equals(downloadLocationEntryValuesStringArray[1])) { // The download location is set to the app directory.
- // Use the public app files directory.
- defaultFilePath = publicAppFilesDirectoryString;
- } else if (downloadLocationString.equals(downloadLocationEntryValuesStringArray[2])) { // The download location is set to the public directory.
- // Use the public download directory.
- defaultFilePath = publicDownloadDirectoryString;
- } else { // The download location is set to custom.
- // Use the download custom location.
- defaultFilePath = downloadCustomLocationString;
- }
-
- // Return the default file path.
- return defaultFilePath;
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
- *
- * Privacy Browser is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.helpers;
-
-import android.net.Uri;
-import android.os.Environment;
-
-public class FileNameHelper {
- public String convertUriToFileNamePath(Uri Uri) {
- // Initialize a file name path string.
- String fileNamePath = "";
-
- // Convert the URI to a raw file name path.
- String rawFileNamePath = Uri.getPath();
-
- // Only process the raw file name path if it is not null.
- if (rawFileNamePath != null) {
- // Check to see if the file name Path includes a valid storage location.
- if (rawFileNamePath.contains(":")) { // The path contains a `:`.
- // 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);
-
- // Check to see if the current file name final patch is a complete, valid path.
- if (fileNameFinalPath.startsWith("/storage/emulated/")) { // The existing file name final path is a complete, valid path.
- // Use the provided file name path as is.
- fileNamePath = fileNameFinalPath;
- } else { // The existing file name final path is not a complete, valid path.
- // 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;
- }
- }
- } else { // The path does not contain a `:`.
- // Use the raw file name path.
- fileNamePath = rawFileNamePath;
- }
- }
-
- // Return the file name path string.
- return fileNamePath;
- }
-}
\ No newline at end of file
/*
- * Copyright © 2018-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2018-2021 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
*
public static final String IMPORT_SUCCESSFUL = "Import Successful";
// Declare the class constants.
- private static final int SCHEMA_VERSION = 12;
+ private static final int SCHEMA_VERSION = 13;
private static final String PREFERENCES_TABLE = "preferences";
// Declare the preferences constants.
private static final String CLEAR_LOGCAT = "clear_logcat";
private static final String CLEAR_CACHE = "clear_cache";
private static final String HOMEPAGE = "homepage";
- private static final String DOWNLOAD_LOCATION = "download_location";
- private static final String DOWNLOAD_CUSTOM_LOCATION = "download_custom_location";
private static final String FONT_SIZE = "font_size";
private static final String OPEN_INTENTS_IN_NEW_TAB = "open_intents_in_new_tab";
private static final String SWIPE_TO_REFRESH = "swipe_to_refresh";
private static final String WIDE_VIEWPORT = "wide_viewport";
private static final String DISPLAY_WEBPAGE_IMAGES = "display_webpage_images";
- public String exportUnencrypted(File exportFile, Context context) {
+ public String importUnencrypted(InputStream importFileInputStream, Context context){
try {
- // Delete the current file if it exists.
- if (exportFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- exportFile.delete();
- }
-
- // Create the export database.
- SQLiteDatabase exportDatabase = SQLiteDatabase.openOrCreateDatabase(exportFile, null);
-
- // Set the export database version number.
- exportDatabase.setVersion(SCHEMA_VERSION);
-
- // Create the export database domains table.
- exportDatabase.execSQL(DomainsDatabaseHelper.CREATE_DOMAINS_TABLE);
-
-
- // Create the export database bookmarks table.
- exportDatabase.execSQL(BookmarksDatabaseHelper.CREATE_BOOKMARKS_TABLE);
-
- // Open the bookmarks database. The `0` specifies the database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
- BookmarksDatabaseHelper bookmarksDatabaseHelper = new BookmarksDatabaseHelper(context, null, null, 0);
-
- // Get a full bookmarks cursor.
- Cursor bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarks();
-
- // Move to the first bookmark.
- bookmarksCursor.moveToFirst();
-
- // Copy the data from the bookmarks cursor into the export database.
- for (int i = 0; i < bookmarksCursor.getCount(); i++) {
- // Extract the record from the cursor and store the data in a ContentValues.
- ContentValues bookmarksContentValues = new ContentValues();
- bookmarksContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)));
- bookmarksContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
- bookmarksContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.PARENT_FOLDER)));
- bookmarksContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)));
- bookmarksContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)));
- bookmarksContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, bookmarksCursor.getBlob(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)));
-
- // Insert the record into the export database.
- exportDatabase.insert(BookmarksDatabaseHelper.BOOKMARKS_TABLE, null, bookmarksContentValues);
-
- // Advance to the next record.
- bookmarksCursor.moveToNext();
- }
-
- // Close the bookmarks database.
- bookmarksCursor.close();
- bookmarksDatabaseHelper.close();
-
-
- // Open the domains database. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
- DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
-
- // Get a full domains database cursor.
- Cursor domainsCursor = domainsDatabaseHelper.getCompleteCursorOrderedByDomain();
-
- // Move to the first domain.
- domainsCursor.moveToFirst();
-
- // Copy the data from the domains cursor into the export database.
- for (int i = 0; i < domainsCursor.getCount(); i++) {
- // Extract the record from the cursor and store the data in a ContentValues.
- ContentValues domainsContentValues = new ContentValues();
- domainsContentValues.put(DomainsDatabaseHelper.DOMAIN_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_JAVASCRIPT, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_DOM_STORAGE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FORM_DATA, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_EASYLIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_EASYPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)));
- domainsContentValues.put(DomainsDatabaseHelper.ULTRALIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)));
- domainsContentValues.put(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)));
- domainsContentValues.put(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)));
- domainsContentValues.put(DomainsDatabaseHelper.USER_AGENT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)));
- domainsContentValues.put(DomainsDatabaseHelper.FONT_SIZE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)));
- domainsContentValues.put(DomainsDatabaseHelper.SWIPE_TO_REFRESH, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)));
- domainsContentValues.put(DomainsDatabaseHelper.WEBVIEW_THEME, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.WEBVIEW_THEME)));
- domainsContentValues.put(DomainsDatabaseHelper.WIDE_VIEWPORT, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT)));
- domainsContentValues.put(DomainsDatabaseHelper.DISPLAY_IMAGES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)));
- domainsContentValues.put(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)));
- domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)));
- domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)));
- domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)));
- domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)));
- domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)));
- domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)));
- domainsContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, domainsCursor.getLong(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
- domainsContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, domainsCursor.getLong(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
- domainsContentValues.put(DomainsDatabaseHelper.PINNED_IP_ADDRESSES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)));
- domainsContentValues.put(DomainsDatabaseHelper.IP_ADDRESSES, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)));
+ // Create a temporary import file.
+ File temporaryImportFile = File.createTempFile("temporary_import_file", null, context.getCacheDir());
- // Insert the record into the export database.
- exportDatabase.insert(DomainsDatabaseHelper.DOMAINS_TABLE, null, domainsContentValues);
+ // Create a temporary file output stream.
+ FileOutputStream temporaryImportFileOutputStream = new FileOutputStream(temporaryImportFile);
- // Advance to the next record.
- domainsCursor.moveToNext();
- }
-
- // Close the domains database.
- domainsCursor.close();
- domainsDatabaseHelper.close();
-
-
- // Prepare the preferences table SQL creation string.
- String CREATE_PREFERENCES_TABLE = "CREATE TABLE " + PREFERENCES_TABLE + " (" +
- _ID + " INTEGER PRIMARY KEY, " +
- JAVASCRIPT + " BOOLEAN, " +
- FIRST_PARTY_COOKIES + " BOOLEAN, " +
- THIRD_PARTY_COOKIES + " BOOLEAN, " +
- DOM_STORAGE + " BOOLEAN, " +
- SAVE_FORM_DATA + " BOOLEAN, " +
- USER_AGENT + " TEXT, " +
- CUSTOM_USER_AGENT + " TEXT, " +
- INCOGNITO_MODE + " BOOLEAN, " +
- DO_NOT_TRACK + " BOOLEAN, " +
- ALLOW_SCREENSHOTS + " BOOLEAN, " +
- EASYLIST + " BOOLEAN, " +
- EASYPRIVACY + " BOOLEAN, " +
- FANBOYS_ANNOYANCE_LIST + " BOOLEAN, " +
- FANBOYS_SOCIAL_BLOCKING_LIST + " BOOLEAN, " +
- ULTRALIST + " BOOLEAN, " +
- ULTRAPRIVACY + " BOOLEAN, " +
- BLOCK_ALL_THIRD_PARTY_REQUESTS + " BOOLEAN, " +
- GOOGLE_ANALYTICS + " BOOLEAN, " +
- FACEBOOK_CLICK_IDS + " BOOLEAN, " +
- TWITTER_AMP_REDIRECTS + " BOOLEAN, " +
- SEARCH + " TEXT, " +
- SEARCH_CUSTOM_URL + " TEXT, " +
- PROXY + " TEXT, " +
- PROXY_CUSTOM_URL + " TEXT, " +
- FULL_SCREEN_BROWSING_MODE + " BOOLEAN, " +
- HIDE_APP_BAR + " BOOLEAN, " +
- CLEAR_EVERYTHING + " BOOLEAN, " +
- CLEAR_COOKIES + " BOOLEAN, " +
- CLEAR_DOM_STORAGE + " BOOLEAN, " +
- CLEAR_FORM_DATA + " BOOLEAN, " +
- CLEAR_LOGCAT + " BOOLEAN, " +
- CLEAR_CACHE + " BOOLEAN, " +
- HOMEPAGE + " TEXT, " +
- DOWNLOAD_LOCATION + " TEXT, " +
- DOWNLOAD_CUSTOM_LOCATION + " TEXT, " +
- FONT_SIZE + " TEXT, " +
- OPEN_INTENTS_IN_NEW_TAB + " BOOLEAN, " +
- SWIPE_TO_REFRESH + " BOOLEAN, " +
- SCROLL_APP_BAR + " BOOLEAN, " +
- DISPLAY_ADDITIONAL_APP_BAR_ICONS + " BOOLEAN, " +
- APP_THEME + " TEXT, " +
- WEBVIEW_THEME + " TEXT, " +
- WIDE_VIEWPORT + " BOOLEAN, " +
- DISPLAY_WEBPAGE_IMAGES + " BOOLEAN)";
-
- // Create the export database preferences table.
- exportDatabase.execSQL(CREATE_PREFERENCES_TABLE);
-
- // Get a handle for the shared preference.
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-
- // Create a ContentValues with the preferences information.
- ContentValues preferencesContentValues = new ContentValues();
- preferencesContentValues.put(JAVASCRIPT, sharedPreferences.getBoolean(JAVASCRIPT, false));
- preferencesContentValues.put(FIRST_PARTY_COOKIES, sharedPreferences.getBoolean(FIRST_PARTY_COOKIES, false));
- preferencesContentValues.put(THIRD_PARTY_COOKIES, sharedPreferences.getBoolean(THIRD_PARTY_COOKIES, false));
- preferencesContentValues.put(DOM_STORAGE, sharedPreferences.getBoolean(DOM_STORAGE, false));
- preferencesContentValues.put(SAVE_FORM_DATA, sharedPreferences.getBoolean(SAVE_FORM_DATA, false)); // Save form data can be removed once the minimum API >= 26.
- preferencesContentValues.put(USER_AGENT, sharedPreferences.getString(USER_AGENT, context.getString(R.string.user_agent_default_value)));
- preferencesContentValues.put(CUSTOM_USER_AGENT, sharedPreferences.getString(CUSTOM_USER_AGENT, context.getString(R.string.custom_user_agent_default_value)));
- preferencesContentValues.put(INCOGNITO_MODE, sharedPreferences.getBoolean(INCOGNITO_MODE, false));
- preferencesContentValues.put(DO_NOT_TRACK, sharedPreferences.getBoolean(DO_NOT_TRACK, false));
- preferencesContentValues.put(ALLOW_SCREENSHOTS, sharedPreferences.getBoolean(ALLOW_SCREENSHOTS, false));
- preferencesContentValues.put(EASYLIST, sharedPreferences.getBoolean(EASYLIST, true));
- preferencesContentValues.put(EASYPRIVACY, sharedPreferences.getBoolean(EASYPRIVACY, true));
- preferencesContentValues.put(FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean(FANBOYS_ANNOYANCE_LIST, true));
- preferencesContentValues.put(FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean(FANBOYS_SOCIAL_BLOCKING_LIST, true));
- preferencesContentValues.put(ULTRALIST, sharedPreferences.getBoolean(ULTRALIST, true));
- preferencesContentValues.put(ULTRAPRIVACY, sharedPreferences.getBoolean(ULTRAPRIVACY, true));
- preferencesContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, false));
- preferencesContentValues.put(GOOGLE_ANALYTICS, sharedPreferences.getBoolean(GOOGLE_ANALYTICS, true));
- preferencesContentValues.put(FACEBOOK_CLICK_IDS, sharedPreferences.getBoolean(FACEBOOK_CLICK_IDS, true));
- preferencesContentValues.put(TWITTER_AMP_REDIRECTS, sharedPreferences.getBoolean(TWITTER_AMP_REDIRECTS, true));
- preferencesContentValues.put(SEARCH, sharedPreferences.getString(SEARCH, context.getString(R.string.search_default_value)));
- preferencesContentValues.put(SEARCH_CUSTOM_URL, sharedPreferences.getString(SEARCH_CUSTOM_URL, context.getString(R.string.search_custom_url_default_value)));
- preferencesContentValues.put(PROXY, sharedPreferences.getString(PROXY, context.getString(R.string.proxy_default_value)));
- preferencesContentValues.put(PROXY_CUSTOM_URL, sharedPreferences.getString(PROXY_CUSTOM_URL, context.getString(R.string.proxy_custom_url_default_value)));
- preferencesContentValues.put(FULL_SCREEN_BROWSING_MODE, sharedPreferences.getBoolean(FULL_SCREEN_BROWSING_MODE, false));
- preferencesContentValues.put(HIDE_APP_BAR, sharedPreferences.getBoolean(HIDE_APP_BAR, true));
- preferencesContentValues.put(CLEAR_EVERYTHING, sharedPreferences.getBoolean(CLEAR_EVERYTHING, true));
- preferencesContentValues.put(CLEAR_COOKIES, sharedPreferences.getBoolean(CLEAR_COOKIES, true));
- preferencesContentValues.put(CLEAR_DOM_STORAGE, sharedPreferences.getBoolean(CLEAR_DOM_STORAGE, true));
- preferencesContentValues.put(CLEAR_FORM_DATA, sharedPreferences.getBoolean(CLEAR_FORM_DATA, true)); // Clear form data can be removed once the minimum API >= 26.
- preferencesContentValues.put(CLEAR_LOGCAT, sharedPreferences.getBoolean(CLEAR_LOGCAT, true));
- preferencesContentValues.put(CLEAR_CACHE, sharedPreferences.getBoolean(CLEAR_CACHE, true));
- preferencesContentValues.put(HOMEPAGE, sharedPreferences.getString(HOMEPAGE, context.getString(R.string.homepage_default_value)));
- preferencesContentValues.put(DOWNLOAD_LOCATION, sharedPreferences.getString(DOWNLOAD_LOCATION, context.getString(R.string.download_location_default_value)));
- preferencesContentValues.put(DOWNLOAD_CUSTOM_LOCATION, sharedPreferences.getString(DOWNLOAD_CUSTOM_LOCATION, context.getString(R.string.download_custom_location_default_value)));
- preferencesContentValues.put(FONT_SIZE, sharedPreferences.getString(FONT_SIZE, context.getString(R.string.font_size_default_value)));
- preferencesContentValues.put(OPEN_INTENTS_IN_NEW_TAB, sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true));
- preferencesContentValues.put(SWIPE_TO_REFRESH, sharedPreferences.getBoolean(SWIPE_TO_REFRESH, true));
- preferencesContentValues.put(SCROLL_APP_BAR, sharedPreferences.getBoolean(SCROLL_APP_BAR, true));
- preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, false));
- preferencesContentValues.put(APP_THEME, sharedPreferences.getString(APP_THEME, context.getString(R.string.app_theme_default_value)));
- preferencesContentValues.put(WEBVIEW_THEME, sharedPreferences.getString(WEBVIEW_THEME, context.getString(R.string.webview_theme_default_value)));
- preferencesContentValues.put(WIDE_VIEWPORT, sharedPreferences.getBoolean(WIDE_VIEWPORT, true));
- preferencesContentValues.put(DISPLAY_WEBPAGE_IMAGES, sharedPreferences.getBoolean(DISPLAY_WEBPAGE_IMAGES, true));
-
- // Insert the preferences into the export database.
- exportDatabase.insert(PREFERENCES_TABLE, null, preferencesContentValues);
-
- // Close the export database.
- exportDatabase.close();
-
- // Convert the database file to a string.
- String exportFileString = exportFile.toString();
-
- // Create strings for the temporary database files.
- String journalFileString = exportFileString + "-journal";
-
- // Get `Files` for the temporary database files.
- File journalFile = new File(journalFileString);
-
- // Delete the Journal file if it exists.
- if (journalFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- journalFile.delete();
- }
-
- // Export successful.
- return EXPORT_SUCCESSFUL;
- } catch (Exception exception) {
- // Return the export error.
- return exception.toString();
- }
- }
-
- public String importUnencrypted(File importFile, Context context){
- try {
- // Create a temporary import file string.
- String temporaryImportFileString = context.getCacheDir() + "/" + "temporary_import_file";
-
- // Get a handle for a temporary import file.
- File temporaryImportFile = new File(temporaryImportFileString);
-
- // Delete the temporary import file if it already exists.
- if (temporaryImportFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- temporaryImportFile.delete();
- }
-
- // Create input and output streams.
- InputStream importFileInputStream = new FileInputStream(importFile);
- OutputStream temporaryImportFileOutputStream = new FileOutputStream(temporaryImportFile);
-
- // Create a byte array.
+ // Create a transfer byte array.
byte[] transferByteArray = new byte[1024];
// Create an integer to track the number of bytes read.
int bytesRead;
- // Copy the import file to the temporary import file. Once the minimum API >= 26 `Files.copy` can be used instead.
+ // Copy the import file to the temporary import file.
while ((bytesRead = importFileInputStream.read(transferByteArray)) > 0) {
temporaryImportFileOutputStream.write(transferByteArray, 0, bytesRead);
}
+ // Flush the temporary import file output stream.
+ temporaryImportFileOutputStream.flush();
+
// Close the file streams.
importFileInputStream.close();
temporaryImportFileOutputStream.close();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
// Open the import database. Once the minimum API >= 27 the file can be opened directly without using the string.
- SQLiteDatabase importDatabase = SQLiteDatabase.openDatabase(temporaryImportFileString, null, SQLiteDatabase.OPEN_READWRITE);
+ SQLiteDatabase importDatabase = SQLiteDatabase.openDatabase(temporaryImportFile.toString(), null, SQLiteDatabase.OPEN_READWRITE);
// Get the database version.
int importDatabaseVersion = importDatabase.getVersion();
// Upgrade the database if needed.
if (importDatabaseVersion < SCHEMA_VERSION) {
- switch (importDatabaseVersion){
+ switch (importDatabaseVersion) {
// Upgrade from schema version 1, Privacy Browser 2.13.
case 1:
// Previously this upgrade added `download_with_external_app` to the Preferences table. But that is now removed in schema version 10.
// Upgrade from schema version 9, Privacy Browser 3.3.
case 9:
- // Add the download location columns to the preferences table.
- importDatabase.execSQL("ALTER TABLE " + PREFERENCES_TABLE + " ADD COLUMN " + DOWNLOAD_LOCATION + " TEXT");
- importDatabase.execSQL("ALTER TABLE " + PREFERENCES_TABLE + " ADD COLUMN " + DOWNLOAD_CUSTOM_LOCATION + " TEXT");
-
- // Get the current download location values.
- String downloadLocation = sharedPreferences.getString(DOWNLOAD_LOCATION, context.getString(R.string.download_location_default_value));
- String downloadCustomLocation = sharedPreferences.getString(DOWNLOAD_CUSTOM_LOCATION, context.getString(R.string.download_custom_location_default_value));
-
- // Populate the preferences table with the current download location values.
- importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + DOWNLOAD_LOCATION + " = '" + downloadLocation + "'");
- importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + DOWNLOAD_CUSTOM_LOCATION + " = '" + downloadCustomLocation + "'");
+ // Previously this upgrade added `download_location` and `download_custom_location` to the Preferences table. But they are now removed in schema version 13.
// Upgrade from schema version 10, Privacy Browser 3.4.
case 10:
} else {
importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + CLEAR_LOGCAT + " = " + 0);
}
+
+ // Upgrade from schema version 12, Privacy Browser 3.6.
+ case 12:
+ // Do nothing. `download_location` and `download_custom_location` were removed from the preferences table.
}
}
.putBoolean(CLEAR_LOGCAT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_LOGCAT)) == 1)
.putBoolean(CLEAR_CACHE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_CACHE)) == 1)
.putString(HOMEPAGE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(HOMEPAGE)))
- .putString(DOWNLOAD_LOCATION, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(DOWNLOAD_LOCATION)))
- .putString(DOWNLOAD_CUSTOM_LOCATION, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(DOWNLOAD_CUSTOM_LOCATION)))
.putString(FONT_SIZE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(FONT_SIZE)))
.putBoolean(OPEN_INTENTS_IN_NEW_TAB, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(OPEN_INTENTS_IN_NEW_TAB)) == 1)
.putBoolean(SWIPE_TO_REFRESH, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(SWIPE_TO_REFRESH)) == 1)
// Close the preferences cursor.
importPreferencesCursor.close();
-
// Close the import database.
importDatabase.close();
- // Create strings for the temporary database files.
- String shmFileString = temporaryImportFileString + "-shm";
- String walFileString = temporaryImportFileString + "-wal";
- String journalFileString = temporaryImportFileString + "-journal";
+ // Delete the temporary import file database, journal, and other related auxiliary files.
+ SQLiteDatabase.deleteDatabase(temporaryImportFile);
+
+ // Import successful.
+ return IMPORT_SUCCESSFUL;
+ } catch (Exception exception) {
+ // Return the import error.
+ return exception.toString();
+ }
+ }
+
+ public String exportUnencrypted(OutputStream exportFileOutputStream, Context context) {
+ try {
+ // Create a temporary export file.
+ File temporaryExportFile = File.createTempFile("temporary_export_file", null, context.getCacheDir());
+
+ // Create the temporary export database.
+ SQLiteDatabase temporaryExportDatabase = SQLiteDatabase.openOrCreateDatabase(temporaryExportFile, null);
+
+ // Set the temporary export database version number.
+ temporaryExportDatabase.setVersion(SCHEMA_VERSION);
- // Get `Files` for the temporary database files.
- File shmFile = new File(shmFileString);
- File walFile = new File(walFileString);
- File journalFile = new File(journalFileString);
- // Delete the Shared Memory file if it exists.
- if (shmFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- shmFile.delete();
+ // Create the temporary export database bookmarks table.
+ temporaryExportDatabase.execSQL(BookmarksDatabaseHelper.CREATE_BOOKMARKS_TABLE);
+
+ // Open the bookmarks database. The `0` specifies the database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+ BookmarksDatabaseHelper bookmarksDatabaseHelper = new BookmarksDatabaseHelper(context, null, null, 0);
+
+ // Get a full bookmarks cursor.
+ Cursor bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarks();
+
+ // Move to the first bookmark.
+ bookmarksCursor.moveToFirst();
+
+ // Copy the data from the bookmarks cursor into the export database.
+ for (int i = 0; i < bookmarksCursor.getCount(); i++) {
+ // Extract the record from the cursor and store the data in a ContentValues.
+ ContentValues bookmarksContentValues = new ContentValues();
+ bookmarksContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)));
+ bookmarksContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
+ bookmarksContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.PARENT_FOLDER)));
+ bookmarksContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)));
+ bookmarksContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)));
+ bookmarksContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, bookmarksCursor.getBlob(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)));
+
+ // Insert the record into the temporary export database.
+ temporaryExportDatabase.insert(BookmarksDatabaseHelper.BOOKMARKS_TABLE, null, bookmarksContentValues);
+
+ // Advance to the next record.
+ bookmarksCursor.moveToNext();
}
- // Delete the Write Ahead Log file if it exists.
- if (walFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- walFile.delete();
+ // Close the bookmarks database.
+ bookmarksCursor.close();
+ bookmarksDatabaseHelper.close();
+
+
+ // Create the temporary export database domains table.
+ temporaryExportDatabase.execSQL(DomainsDatabaseHelper.CREATE_DOMAINS_TABLE);
+
+ // Open the domains database. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+ DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
+
+ // Get a full domains database cursor.
+ Cursor domainsCursor = domainsDatabaseHelper.getCompleteCursorOrderedByDomain();
+
+ // Move to the first domain.
+ domainsCursor.moveToFirst();
+
+ // Copy the data from the domains cursor into the export database.
+ for (int i = 0; i < domainsCursor.getCount(); i++) {
+ // Extract the record from the cursor and store the data in a ContentValues.
+ ContentValues domainsContentValues = new ContentValues();
+ domainsContentValues.put(DomainsDatabaseHelper.DOMAIN_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_JAVASCRIPT, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_DOM_STORAGE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FORM_DATA, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_EASYLIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_EASYPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)));
+ domainsContentValues.put(DomainsDatabaseHelper.ULTRALIST, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)));
+ domainsContentValues.put(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)));
+ domainsContentValues.put(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)));
+ domainsContentValues.put(DomainsDatabaseHelper.USER_AGENT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)));
+ domainsContentValues.put(DomainsDatabaseHelper.FONT_SIZE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)));
+ domainsContentValues.put(DomainsDatabaseHelper.SWIPE_TO_REFRESH, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)));
+ domainsContentValues.put(DomainsDatabaseHelper.WEBVIEW_THEME, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.WEBVIEW_THEME)));
+ domainsContentValues.put(DomainsDatabaseHelper.WIDE_VIEWPORT, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT)));
+ domainsContentValues.put(DomainsDatabaseHelper.DISPLAY_IMAGES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)));
+ domainsContentValues.put(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)));
+ domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)));
+ domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)));
+ domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)));
+ domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)));
+ domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)));
+ domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)));
+ domainsContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, domainsCursor.getLong(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+ domainsContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, domainsCursor.getLong(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+ domainsContentValues.put(DomainsDatabaseHelper.PINNED_IP_ADDRESSES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)));
+ domainsContentValues.put(DomainsDatabaseHelper.IP_ADDRESSES, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)));
+
+ // Insert the record into the temporary export database.
+ temporaryExportDatabase.insert(DomainsDatabaseHelper.DOMAINS_TABLE, null, domainsContentValues);
+
+ // Advance to the next record.
+ domainsCursor.moveToNext();
}
- // Delete the Journal file if it exists.
- if (journalFile.exists()) {
- //noinspection ResultOfMethodCallIgnored
- journalFile.delete();
+ // Close the domains database.
+ domainsCursor.close();
+ domainsDatabaseHelper.close();
+
+
+ // Prepare the preferences table SQL creation string.
+ String CREATE_PREFERENCES_TABLE = "CREATE TABLE " + PREFERENCES_TABLE + " (" +
+ _ID + " INTEGER PRIMARY KEY, " +
+ JAVASCRIPT + " BOOLEAN, " +
+ FIRST_PARTY_COOKIES + " BOOLEAN, " +
+ THIRD_PARTY_COOKIES + " BOOLEAN, " +
+ DOM_STORAGE + " BOOLEAN, " +
+ SAVE_FORM_DATA + " BOOLEAN, " +
+ USER_AGENT + " TEXT, " +
+ CUSTOM_USER_AGENT + " TEXT, " +
+ INCOGNITO_MODE + " BOOLEAN, " +
+ DO_NOT_TRACK + " BOOLEAN, " +
+ ALLOW_SCREENSHOTS + " BOOLEAN, " +
+ EASYLIST + " BOOLEAN, " +
+ EASYPRIVACY + " BOOLEAN, " +
+ FANBOYS_ANNOYANCE_LIST + " BOOLEAN, " +
+ FANBOYS_SOCIAL_BLOCKING_LIST + " BOOLEAN, " +
+ ULTRALIST + " BOOLEAN, " +
+ ULTRAPRIVACY + " BOOLEAN, " +
+ BLOCK_ALL_THIRD_PARTY_REQUESTS + " BOOLEAN, " +
+ GOOGLE_ANALYTICS + " BOOLEAN, " +
+ FACEBOOK_CLICK_IDS + " BOOLEAN, " +
+ TWITTER_AMP_REDIRECTS + " BOOLEAN, " +
+ SEARCH + " TEXT, " +
+ SEARCH_CUSTOM_URL + " TEXT, " +
+ PROXY + " TEXT, " +
+ PROXY_CUSTOM_URL + " TEXT, " +
+ FULL_SCREEN_BROWSING_MODE + " BOOLEAN, " +
+ HIDE_APP_BAR + " BOOLEAN, " +
+ CLEAR_EVERYTHING + " BOOLEAN, " +
+ CLEAR_COOKIES + " BOOLEAN, " +
+ CLEAR_DOM_STORAGE + " BOOLEAN, " +
+ CLEAR_FORM_DATA + " BOOLEAN, " +
+ CLEAR_LOGCAT + " BOOLEAN, " +
+ CLEAR_CACHE + " BOOLEAN, " +
+ HOMEPAGE + " TEXT, " +
+ FONT_SIZE + " TEXT, " +
+ OPEN_INTENTS_IN_NEW_TAB + " BOOLEAN, " +
+ SWIPE_TO_REFRESH + " BOOLEAN, " +
+ SCROLL_APP_BAR + " BOOLEAN, " +
+ DISPLAY_ADDITIONAL_APP_BAR_ICONS + " BOOLEAN, " +
+ APP_THEME + " TEXT, " +
+ WEBVIEW_THEME + " TEXT, " +
+ WIDE_VIEWPORT + " BOOLEAN, " +
+ DISPLAY_WEBPAGE_IMAGES + " BOOLEAN)";
+
+ // Create the temporary export database preferences table.
+ temporaryExportDatabase.execSQL(CREATE_PREFERENCES_TABLE);
+
+ // Get a handle for the shared preference.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // Create a ContentValues with the preferences information.
+ ContentValues preferencesContentValues = new ContentValues();
+ preferencesContentValues.put(JAVASCRIPT, sharedPreferences.getBoolean(JAVASCRIPT, false));
+ preferencesContentValues.put(FIRST_PARTY_COOKIES, sharedPreferences.getBoolean(FIRST_PARTY_COOKIES, false));
+ preferencesContentValues.put(THIRD_PARTY_COOKIES, sharedPreferences.getBoolean(THIRD_PARTY_COOKIES, false));
+ preferencesContentValues.put(DOM_STORAGE, sharedPreferences.getBoolean(DOM_STORAGE, false));
+ preferencesContentValues.put(SAVE_FORM_DATA, sharedPreferences.getBoolean(SAVE_FORM_DATA, false)); // Save form data can be removed once the minimum API >= 26.
+ preferencesContentValues.put(USER_AGENT, sharedPreferences.getString(USER_AGENT, context.getString(R.string.user_agent_default_value)));
+ preferencesContentValues.put(CUSTOM_USER_AGENT, sharedPreferences.getString(CUSTOM_USER_AGENT, context.getString(R.string.custom_user_agent_default_value)));
+ preferencesContentValues.put(INCOGNITO_MODE, sharedPreferences.getBoolean(INCOGNITO_MODE, false));
+ preferencesContentValues.put(DO_NOT_TRACK, sharedPreferences.getBoolean(DO_NOT_TRACK, false));
+ preferencesContentValues.put(ALLOW_SCREENSHOTS, sharedPreferences.getBoolean(ALLOW_SCREENSHOTS, false));
+ preferencesContentValues.put(EASYLIST, sharedPreferences.getBoolean(EASYLIST, true));
+ preferencesContentValues.put(EASYPRIVACY, sharedPreferences.getBoolean(EASYPRIVACY, true));
+ preferencesContentValues.put(FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean(FANBOYS_ANNOYANCE_LIST, true));
+ preferencesContentValues.put(FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean(FANBOYS_SOCIAL_BLOCKING_LIST, true));
+ preferencesContentValues.put(ULTRALIST, sharedPreferences.getBoolean(ULTRALIST, true));
+ preferencesContentValues.put(ULTRAPRIVACY, sharedPreferences.getBoolean(ULTRAPRIVACY, true));
+ preferencesContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, false));
+ preferencesContentValues.put(GOOGLE_ANALYTICS, sharedPreferences.getBoolean(GOOGLE_ANALYTICS, true));
+ preferencesContentValues.put(FACEBOOK_CLICK_IDS, sharedPreferences.getBoolean(FACEBOOK_CLICK_IDS, true));
+ preferencesContentValues.put(TWITTER_AMP_REDIRECTS, sharedPreferences.getBoolean(TWITTER_AMP_REDIRECTS, true));
+ preferencesContentValues.put(SEARCH, sharedPreferences.getString(SEARCH, context.getString(R.string.search_default_value)));
+ preferencesContentValues.put(SEARCH_CUSTOM_URL, sharedPreferences.getString(SEARCH_CUSTOM_URL, context.getString(R.string.search_custom_url_default_value)));
+ preferencesContentValues.put(PROXY, sharedPreferences.getString(PROXY, context.getString(R.string.proxy_default_value)));
+ preferencesContentValues.put(PROXY_CUSTOM_URL, sharedPreferences.getString(PROXY_CUSTOM_URL, context.getString(R.string.proxy_custom_url_default_value)));
+ preferencesContentValues.put(FULL_SCREEN_BROWSING_MODE, sharedPreferences.getBoolean(FULL_SCREEN_BROWSING_MODE, false));
+ preferencesContentValues.put(HIDE_APP_BAR, sharedPreferences.getBoolean(HIDE_APP_BAR, true));
+ preferencesContentValues.put(CLEAR_EVERYTHING, sharedPreferences.getBoolean(CLEAR_EVERYTHING, true));
+ preferencesContentValues.put(CLEAR_COOKIES, sharedPreferences.getBoolean(CLEAR_COOKIES, true));
+ preferencesContentValues.put(CLEAR_DOM_STORAGE, sharedPreferences.getBoolean(CLEAR_DOM_STORAGE, true));
+ preferencesContentValues.put(CLEAR_FORM_DATA, sharedPreferences.getBoolean(CLEAR_FORM_DATA, true)); // Clear form data can be removed once the minimum API >= 26.
+ preferencesContentValues.put(CLEAR_LOGCAT, sharedPreferences.getBoolean(CLEAR_LOGCAT, true));
+ preferencesContentValues.put(CLEAR_CACHE, sharedPreferences.getBoolean(CLEAR_CACHE, true));
+ preferencesContentValues.put(HOMEPAGE, sharedPreferences.getString(HOMEPAGE, context.getString(R.string.homepage_default_value)));
+ preferencesContentValues.put(FONT_SIZE, sharedPreferences.getString(FONT_SIZE, context.getString(R.string.font_size_default_value)));
+ preferencesContentValues.put(OPEN_INTENTS_IN_NEW_TAB, sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true));
+ preferencesContentValues.put(SWIPE_TO_REFRESH, sharedPreferences.getBoolean(SWIPE_TO_REFRESH, true));
+ preferencesContentValues.put(SCROLL_APP_BAR, sharedPreferences.getBoolean(SCROLL_APP_BAR, true));
+ preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, false));
+ preferencesContentValues.put(APP_THEME, sharedPreferences.getString(APP_THEME, context.getString(R.string.app_theme_default_value)));
+ preferencesContentValues.put(WEBVIEW_THEME, sharedPreferences.getString(WEBVIEW_THEME, context.getString(R.string.webview_theme_default_value)));
+ preferencesContentValues.put(WIDE_VIEWPORT, sharedPreferences.getBoolean(WIDE_VIEWPORT, true));
+ preferencesContentValues.put(DISPLAY_WEBPAGE_IMAGES, sharedPreferences.getBoolean(DISPLAY_WEBPAGE_IMAGES, true));
+
+ // Insert the preferences into the temporary export database.
+ temporaryExportDatabase.insert(PREFERENCES_TABLE, null, preferencesContentValues);
+
+ // Close the temporary export database.
+ temporaryExportDatabase.close();
+
+
+ // Create the temporary export file input stream.
+ FileInputStream temporaryExportFileInputStream = new FileInputStream(temporaryExportFile);
+
+ // Create a byte array.
+ byte[] transferByteArray = new byte[1024];
+
+ // Create an integer to track the number of bytes read.
+ int bytesRead;
+
+ // Copy the temporary export file to the export file output stream.
+ while ((bytesRead = temporaryExportFileInputStream.read(transferByteArray)) > 0) {
+ exportFileOutputStream.write(transferByteArray, 0, bytesRead);
}
- // Delete the temporary import file.
- //noinspection ResultOfMethodCallIgnored
- temporaryImportFile.delete();
+ // Flush the export file output stream.
+ exportFileOutputStream.flush();
- // Import successful.
- return IMPORT_SUCCESSFUL;
+ // Close the file streams.
+ temporaryExportFileInputStream.close();
+ exportFileOutputStream.close();
+
+ // Delete the temporary export file database, journal, and other related auxiliary files.
+ SQLiteDatabase.deleteDatabase(temporaryExportFile);
+
+ // Export successful.
+ return EXPORT_SUCCESSFUL;
} catch (Exception exception) {
- // Return the import error.
+ // Return the export error.
return exception.toString();
}
}
+++ /dev/null
-<!-- This file comes from the Android Material icon set, where it is called `file_download`. 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"
- android:viewportWidth="24" >
-
- <!-- A hard coded color must be used until API >= 21. Then `@color` or `?attr/colorControlNormal` may be used. -->
- <path
- android:fillColor="#FFB7B7B7"
- android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
-</vector>
\ No newline at end of file
+++ /dev/null
-<!-- This file comes from the Android Material icon set, where it is called `file_download`. 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"
- android:viewportWidth="24" >
-
- <!-- A hard coded color must be used until API >= 21. Then `@color` or `?attr/colorControlNormal` may be used. -->
- <path
- android:fillColor="#FF616161"
- android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
-</vector>
\ No newline at end of file
--- /dev/null
+<!-- This file comes from the Android Material icon set, where it is called `import_export`. 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 API >= 21. Then `@color` or `?attr/colorControlNormal` may be used instead. -->
+ <path
+ android:fillColor="#FF5785C5"
+ android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z" />
+</vector>
\ No newline at end of file
+++ /dev/null
-<!-- This file comes from the Android Material icon set, where it is called `import_export`. 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 API >= 21. Then `@color` or `?attr/colorControlNormal` may be used instead. -->
- <path
- android:fillColor="#FF5785C5"
- android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z" />
-</vector>
\ No newline at end of file
+++ /dev/null
-<!-- This file comes from the Android Material icon set, where it is called `import_export`. 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 API >= 21. Then `@color` or `?attr/colorControlNormal` may be used instead. -->
- <path
- android:fillColor="#FF8AB4F8"
- android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z" />
-</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright © 2018-2020 Soren Stoutner <soren@stoutner.com>.
+ Copyright © 2018-2021 Soren Stoutner <soren@stoutner.com>.
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
<!-- The encryption password. -->
<com.google.android.material.textfield.TextInputLayout
- android:id="@+id/password_encryption_textinputlayout"
+ android:id="@+id/encryption_password_textinputlayout"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:passwordToggleEnabled="true" >
<com.google.android.material.textfield.TextInputEditText
- android:id="@+id/password_encryption_edittext"
+ android:id="@+id/encryption_password_edittext"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:hint="@string/password"
android:onClick="browse" />
</LinearLayout>
- <!-- File notices. -->
- <TextView
- android:id="@+id/file_does_not_exist_textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_margin="5dp"
- android:text="@string/file_does_not_exist"
- android:textColor="?attr/redTextColor"
- android:textAlignment="center" />
-
- <TextView
- android:id="@+id/file_exists_warning_textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_margin="5dp"
- android:text="@string/file_exists_warning"
- android:textColor="?attr/redTextColor"
- android:textAlignment="center" />
-
<!-- OpenKeychain import instructions -->
<TextView
android:id="@+id/openkeychain_import_instructions_textview"
android:textColor="?attr/buttonTextColorSelector" />
</LinearLayout>
</androidx.cardview.widget.CardView>
-
- <TextView
- android:id="@+id/import_export_storage_permission_textview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_marginBottom="10dp"
- android:layout_marginStart="15dp"
- android:layout_marginEnd="15dp"
- android:text="@string/storage_permission_explanation"
- android:textAlignment="center" />
</LinearLayout>
</ScrollView>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
+ Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
android:layout_height="wrap_content"
android:layout_width="match_parent" >
+ <!-- Align the edit text and the select file button horizontally. -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:orientation="vertical"
+ android:orientation="horizontal"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" >
- <!-- Align the edit text and the select file button horizontally. -->
- <LinearLayout
+ <!-- The text input layout makes the `android:hint` float above the edit text. -->
+ <com.google.android.material.textfield.TextInputLayout
android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:orientation="horizontal" >
+ android:layout_width="0dp"
+ android:layout_weight="1" >
- <!-- The text input layout makes the `android:hint` float above the edit text. -->
- <com.google.android.material.textfield.TextInputLayout
+ <!-- `android:inputType="textUri"` disables spell check and places an `/` on the main keyboard. -->
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/file_name_edittext"
android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:layout_weight="1" >
+ android:layout_width="match_parent"
+ android:hint="@string/file_name"
+ android:inputType="textMultiLine|textUri" />
+ </com.google.android.material.textfield.TextInputLayout>
- <!-- `android:inputType="textUri"` disables spell check and places an `/` on the main keyboard. -->
- <com.google.android.material.textfield.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" />
- </com.google.android.material.textfield.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>
-
- <!-- File does not exist warning. -->
- <TextView
- android:id="@+id/file_does_not_exist_textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_margin="5dp"
- android:text="@string/file_does_not_exist"
- android:textColor="?attr/redTextColor"
- android:textAlignment="center" />
-
- <!-- Storage permission explanation. -->
- <TextView
- android:id="@+id/storage_permission_textview"
+ <Button
+ android:id="@+id/browse_button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:text="@string/storage_permission_explanation"
- android:textAlignment="center" />
+ android:layout_gravity="center_vertical"
+ android:text="@string/browse" />
</LinearLayout>
</ScrollView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
+ Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
android:layout_height="wrap_content"
android:layout_width="match_parent" >
+ <!-- Align the edit text and the select file button horizontally. -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:orientation="vertical"
+ android:orientation="horizontal"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" >
- <!-- Align the edit text and the select file button horizontally. -->
- <LinearLayout
+ <!-- The text input layout makes the `android:hint` float above the edit text. -->
+ <com.google.android.material.textfield.TextInputLayout
android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:orientation="horizontal" >
+ android:layout_width="0dp"
+ android:layout_weight="1" >
- <!-- The text input layout makes the `android:hint` float above the edit text. -->
- <com.google.android.material.textfield.TextInputLayout
+ <!-- `android:inputType="textUri"` disables spell check and places an `/` on the main keyboard. -->
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/file_name_edittext"
android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:layout_weight="1" >
+ android:layout_width="match_parent"
+ android:hint="@string/file_name"
+ android:inputType="textMultiLine|textUri" />
+ </com.google.android.material.textfield.TextInputLayout>
- <!-- `android:inputType="textUri"` disables spell check and places an `/` on the main keyboard. -->
- <com.google.android.material.textfield.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" />
- </com.google.android.material.textfield.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>
-
- <!-- File already exists warning. -->
- <TextView
- android:id="@+id/file_exists_warning_textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_margin="5dp"
- android:text="@string/file_exists_warning"
- android:textColor="?attr/redTextColor"
- android:textAlignment="center" />
-
- <!-- Storage permission explanation. -->
- <TextView
- android:id="@+id/storage_permission_textview"
+ <Button
+ android:id="@+id/browse_button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:text="@string/storage_permission_explanation"
- android:textAlignment="center" />
+ android:layout_gravity="center_vertical"
+ android:text="@string/browse" />
</LinearLayout>
</ScrollView>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>.
-
- This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
-
- Privacy Browser is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Privacy Browser is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>. -->
-
-<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" >
-
- <!-- The text input layout makes the `android:hint` float above the edit text. -->
- <com.google.android.material.textfield.TextInputLayout
- android:id="@+id/url_textinputlayout"
- android:layout_height="wrap_content"
- android:layout_width="match_parent" >
-
- <!-- `android:inputType="TextUri"` disables spell check and places an `/` on the main keyboard. -->
- <com.google.android.material.textfield.TextInputEditText
- android:id="@+id/url_edittext"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:hint="@string/url"
- android:inputType="textMultiLine|textUri" />
- </com.google.android.material.textfield.TextInputLayout>
-
- <!-- File size. -->
- <TextView
- android:id="@+id/file_size_textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginEnd="3dp"
- android:layout_marginBottom="5dp"
- android:layout_gravity="end" />
-
- <!-- Align the edit text and the select file button horizontally. -->
- <LinearLayout
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:orientation="horizontal" >
-
- <!-- The text input layout makes the `android:hint` float above the edit text. -->
- <com.google.android.material.textfield.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. -->
- <com.google.android.material.textfield.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" />
- </com.google.android.material.textfield.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>
-
- <!-- File already exists warning. -->
- <TextView
- android:id="@+id/file_exists_warning_textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_margin="5dp"
- android:text="@string/file_exists_warning"
- android:textColor="?attr/redTextColor"
- android:textAlignment="center" />
-
- <!-- Storage permission explanation. -->
- <TextView
- android:id="@+id/storage_permission_textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:text="@string/storage_permission_explanation"
- android:textAlignment="center" />
- </LinearLayout>
-</ScrollView>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright © 2019-2021 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" >
+
+ <!-- The text input layout makes the `android:hint` float above the edit text. -->
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/url_textinputlayout"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent" >
+
+ <!-- `android:inputType="TextUri"` disables spell check and places an `/` on the main keyboard. -->
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/url_edittext"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:hint="@string/url"
+ android:inputType="textMultiLine|textUri" />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <!-- File size. -->
+ <TextView
+ android:id="@+id/file_size_textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginEnd="3dp"
+ android:layout_marginBottom="5dp"
+ android:layout_gravity="end" />
+
+ <!-- Align the edit text and the select file button horizontally. -->
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:orientation="horizontal" >
+
+ <!-- The text input layout makes the `android:hint` float above the edit text. -->
+ <com.google.android.material.textfield.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. -->
+ <com.google.android.material.textfield.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" />
+ </com.google.android.material.textfield.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>
+ </LinearLayout>
+</ScrollView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
<item
android:id="@+id/import_export"
android:title="@string/import_export"
- android:icon="@drawable/import_export_day"
+ android:icon="@drawable/import_export"
android:orderInCategory="110" />
<item
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright © 2015-2020 Soren Stoutner <soren@stoutner.com>.
+ Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
android:title="@string/save_url"
android:orderInCategory="1201"
app:showAsAction="never" />
- <item
- android:id="@+id/save_archive"
- android:title="@string/save_archive"
- android:orderInCategory="1202"
- app:showAsAction="never" />
<item
android:id="@+id/save_image"
android:title="@string/save_image"
- android:orderInCategory="1203"
+ android:orderInCategory="1202"
app:showAsAction="never" />
</menu>
</item>
<!-- MainWebViewActivity Navigation Menu. -->
<string name="navigation_drawer">Navigationspanel</string>
- <string name="navigation">Navigation</string>
<string name="clear_and_exit">Leeren und verlassen</string>
<string name="home">Startseite</string>
<string name="back">Zurück</string>
<!-- Save Dialogs. -->
<string name="save_url">URL speichern</string>
- <string name="save_archive">Archiv speichern</string>
<string name="save_text">Text speichern</string>
<string name="save_image">Grafik speichern</string>
<string name="save_logcat">Logcat speichern</string>
<string name="file_name">Dateiname</string>
- <string name="webpage_mht">Webseite.mht</string>
<string name="webpage_png">Webseite.png</string>
<string name="privacy_browser_logcat_txt">Privacy Browser Logcat.txt</string>
<string name="privacy_browser_version_txt">Privacy Browser Version.txt</string>
<string name="bytes">Bytes</string>
<string name="unknown_size">Unbekannte Größe</string>
<string name="invalid_url">Ungültige URL</string>
- <string name="ok">OK</string>
<string name="saving_file">Speichere Datei:</string>
<string name="processing_image">Bild wird bearbeitet… :</string>
<string name="file_saved">Datei gespeichert:</string>
<item>OpenPGP</item>
</string-array>
<string name="kitkat_password_encryption_message">Passwort-Verschlüsselung ist mit Android KitKat nicht möglich.</string>
- <string name="file_does_not_exist">Die Datei existiert nicht.</string>
- <string name="file_exists_warning">Die Datei existiert bereits. Wenn Sie fortfahren, wird sie überschrieben.</string>
<string name="openkeychain_required">Für die OpenPGP-Verschlüsselung muss OpenKeychain installiert sein.</string>
<string name="openkeychain_import_instructions">Die unverschlüsselte Datei muss in einem weiteren Schritt importiert werden, nachdem sie entschlüsselt wurde.</string>
<string name="file_location">Datei-Ordner</string>
<string name="export_successful">Export erfolgreich.</string>
<string name="export_failed">Export fehlgeschlagen:</string>
<string name="import_failed">Import fehlgeschlagen:</string>
- <string name="storage_permission">Speicher-Berechtigung</string>
- <string name="storage_permission_message">Privacy Browser benötigt die Speicher-Berechtigung, um auf öffentliche Ordner zuzugreifen.
- Wenn diese verweigert wird, können die Ordner der Anwendung trotzdem verwendet werden.</string>
- <string name="storage_permission_explanation">Der Zugriff auf Dateien in öffentlichen Ordnern benötigt die Speicher-Berechtigung. Andernfalls können nur die Ordner der App verwendet werden.</string>
- <string name="cannot_use_location">Dieser Ordner kann nicht genutzt werden, da die Speicher-Berechtigung nicht erteilt wurde.</string>
<!-- Logcat. -->
<string name="copy_string">kopieren</string> <!-- `copy` is a reserved word and should not be used as the name. -->
<string name="clear_cache_summary">Löscht den WebView-Cache.</string>
<string name="general">Allgemein</string>
<string name="homepage">Startseite</string>
- <string name="download_location">Speicher-Ort für Downloads</string>
- <string-array name="download_location_entries">
- <item>Automatisch</item>
- <item>App-Ordner</item>
- <item>Öffentlicher Ordner</item>
- <item>Benutzerdefiniert</item>
- </string-array>
- <string name="download_custom_location">Benutzerdefinierter Ordner für Downloads</string>
<string name="font_size_preference">Schriftgröße</string>
<string name="open_intents_in_new_tab">Intents in neuem Tab öffnen</string>
<string name="open_intents_in_new_tab_summary">Intents sind Links, die von anderen Apps übergeben werden.</string>
<!-- MainWebViewActivity Navigation Menu. -->
<string name="navigation_drawer">Caja de navegación</string>
- <string name="navigation">Navegación</string>
<string name="clear_and_exit">Borrar y salir</string>
<string name="home">Inicio</string>
<string name="back">Atrás</string>
<!-- Save Dialogs. -->
<string name="save_url">Guardar URL</string>
- <string name="save_archive">Guardar archivo</string>
<string name="save_text">Guardar texto</string>
<string name="save_image">Guardar imagen</string>
<string name="save_logcat">Guardar logcat</string>
<string name="file_name">Nombre de archivo</string>
- <string name="webpage_mht">PaginaWeb.mht</string>
<string name="webpage_png">PaginaWeb.png</string>
<string name="privacy_browser_logcat_txt">Navegador Privado Logcat.txt</string>
<string name="privacy_browser_version_txt">Versión de Navegador Privado.txt</string>
<string name="bytes">bytes</string>
<string name="unknown_size">Tamaño desconocido</string>
<string name="invalid_url">URL inválida</string>
- <string name="ok">OK</string>
<string name="saving_file">Guardando archivo:</string>
<string name="processing_image">Procesando imagen… :</string>
<string name="file_saved">Archivo guardado:</string>
<item>OpenPGP</item>
</string-array>
<string name="kitkat_password_encryption_message">El cifrado de contraseñas no funciona en Android KitKat.</string>
- <string name="file_does_not_exist">El archivo no existe.</string>
- <string name="file_exists_warning">El archivo ya existe. Si procede, se sobrescribirá.</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="file_location">Ubicación del archivo</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="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.
- Si se deniega, los directorios de la aplicación pueden seguir utilizándose.</string>
- <string name="storage_permission_explanation">El acceso a los archivos en directorios públicos requiere el permiso de almacenamiento.
- De lo contrario, sólo funcionarán los directorios de aplicaciones.</string>
- <string name="cannot_use_location">Esta ubicación no se puede utilizar porque no se ha concedido el permiso de almacenamiento.</string>
<!-- Logcat. -->
<string name="copy_string">Copiar</string> <!-- `copy` is a reserved word and should not be used as the name. -->
<string name="clear_cache_summary">Borra la caché de WebView.</string>
<string name="general">General</string>
<string name="homepage">Página de inicio</string>
- <string name="download_location">Lugar de descarga</string>
- <string-array name="download_location_entries">
- <item>Auto</item>
- <item>Directorio de aplicaciones</item>
- <item>Directorio público</item>
- <item>Personalizado</item>
- </string-array>
- <string name="download_custom_location">Lugar personalizado de descarga</string>
<string name="font_size_preference">Tamaño de fuente</string>
<string name="open_intents_in_new_tab">Abrir contenido en nueva pestaña</string>
<string name="open_intents_in_new_tab_summary">Los contenidos son enlaces enviados desde otras apps.</string>
<!-- MainWebViewActivity Navigation Menu. -->
<string name="navigation_drawer">Panneau de navigation</string>
- <string name="navigation">Navigation</string>
<string name="clear_and_exit">Nettoyer et quitter</string>
<string name="home">Accueil</string>
<string name="back">Précédent</string>
<!-- Save Dialogs. -->
<string name="save_url">Enregistrer l\'URL</string>
- <string name="save_archive">Enregistrer l\'archive</string>
<string name="save_text">Sauvegarder le texte</string>
<string name="save_image">Sauvegarder en tant qu\'image</string>
<string name="save_logcat">Sauvegarder le journal système</string>
<string name="file_name">Nom du fichier</string>
- <string name="webpage_mht">PageWeb.mht</string>
<string name="webpage_png">PageWeb.png</string>
<string name="privacy_browser_logcat_txt">Privacy Browser Logcat.txt</string>
<string name="privacy_browser_version_txt">Privacy Browser Version.txt</string>
<string name="bytes">octets</string>
<string name="unknown_size">taille inconnue</string>
<string name="invalid_url">URL invalide</string>
- <string name="ok">OK</string>
<string name="saving_file">Enregistrement du fichier:</string>
<string name="processing_image">Traitement de l\'image… :</string>
<string name="file_saved">Fichier enregistré:</string>
<item>OpenPGP</item>
</string-array>
<string name="kitkat_password_encryption_message">Le chiffrement par mot de passe ne fonctionne pas sous Android KitKat.</string>
- <string name="file_does_not_exist">Le fichier n\'existe pas.</string>
- <string name="file_exists_warning">Le fichier existe déjà. Si vous continuez, il sera écrasé.</string>
<string name="openkeychain_required">Le chiffrement OpenPGP nécessite l\'installation d\'OpenKeychain.</string>
<string name="openkeychain_import_instructions">Le fichier non-chiffré devra être importé dans un deuxième temps, après son déchiffrement.</string>
<string name="file_location">Emplacement du fichier</string>
<string name="export_successful">Export effectué.</string>
<string name="export_failed">L\'export a échoué :</string>
<string name="import_failed">L\'import a échoué :</string>
- <string name="storage_permission">Permission de stockage</string>
- <string name="storage_permission_message">Privacy Browser nécessite les droits d\'accès au stockage pour accéder aux dossiers publics.
- Si cela est refusé, les dossiers internes à l\'application peut néanmoins être utilisé.</string>
- <string name="storage_permission_explanation">Accéder à des fichiers dans des dossiers publics nécessite des droits de lecture/écriture.
- Autrement, seuls les dossiers internes à l\'application ne pourront être utilisés.</string>
- <string name="cannot_use_location">Ce dossier ne peut pas être utilisé car les droits d\'accès au stockage n\'ont pas été autorisés.</string>
<!-- Logcat. -->
<string name="copy_string">Copie</string> <!-- `copy` is a reserved word and should not be used as the name. -->
<string name="clear_cache_summary">Efface le cache WebView.</string>
<string name="general">General</string>
<string name="homepage">Page d\'accueil</string>
- <string name="download_location">Emplacement de téléchargement</string>
- <string-array name="download_location_entries">
- <item>Automatique</item>
- <item>Dossier de l\'application</item>
- <item>Dossier publique</item>
- <item>Personnalisé</item>
- </string-array>
- <string name="download_custom_location">Emplacement personnalisé de téléchargement</string>
<string name="font_size_preference">Zoom</string>
<string name="open_intents_in_new_tab">Intentions dans un nouvel onglet</string>
<string name="open_intents_in_new_tab_summary">Les intentions sont des liens envoyés à partir d\'autres applications.</string>
<!-- MainWebViewActivity Navigation Menu. -->
<string name="navigation_drawer">Menù di navigazione</string>
- <string name="navigation">Navigazione</string>
<string name="clear_and_exit">Elimina dati ed esci</string>
<string name="home">Home</string>
<string name="back">Indietro</string>
<!-- Save Dialogs. -->
<string name="save_url">Salva URL</string>
- <string name="save_archive">Salva Archivio</string>
<string name="save_text">Salva Testo</string>
<string name="save_image">Salva Immagine</string>
<string name="save_logcat">Salva il log</string>
<string name="file_name">Nome File</string>
- <string name="webpage_mht">PaginaWeb.mht</string>
<string name="webpage_png">PaginaWeb.png</string>
<string name="privacy_browser_logcat_txt">Privacy Browser Logcat.txt</string>
<string name="privacy_browser_version_txt">Versione di Privacy Browser.txt</string>
<string name="bytes">byte</string>
<string name="unknown_size">Dimensione sconosciuta</string>
<string name="invalid_url">URL non valida</string>
- <string name="ok">OK</string>
<string name="saving_file">Salvataggio file:</string>
<string name="processing_image">Creazione immagine… :</string>
<string name="file_saved">File salvato:</string>
<item>OpenPGP</item>
</string-array>
<string name="kitkat_password_encryption_message">La cifratura delle Password non funziona su Android KitKat.</string>
- <string name="file_does_not_exist">Il file non esiste.</string>
- <string name="file_exists_warning">Il file è già esistente. Se si decide di procedere sarà sovrascritto.</string>
<string name="openkeychain_required">La cifratura OpenPGP richiede l\'installazione di OpenKeychain.</string>
<string name="openkeychain_import_instructions">Il file non cifrato deve essere importato in un secondo momento dopo che è stato decriptato.</string>
<string name="file_location">Posizione del File</string>
<string name="export_successful">Esportazione riuscita</string>
<string name="export_failed">Esportazione fallita:</string>
<string name="import_failed">Importazione fallita:</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.
- Se questo permesso è negato possono comunque essere utilizzate le cartelle dell\'applicazione.</string>
- <string name="storage_permission_explanation">L\'accesso ai file posti in cartelle pubbliche richiede il permesso di accesso alla memoria,
- altrimenti saranno accessibili solo le cartelle dell\'applicazione.</string>
- <string name="cannot_use_location">Questa posizione non può essere utilizzata perché non è stato concesso il permesso di scrittura in memoria.</string>
<!-- Logcat. -->
<string name="copy_string">Copia</string> <!-- `copy` is a reserved word and should not be used as the name. -->
<string name="clear_cache_summary">Cancella solo la cache di WebView.</string>
<string name="general">Generale</string>
<string name="homepage">Homepage</string>
- <string name="download_location">Posizione dei Download</string>
- <string-array name="download_location_entries">
- <item>Automatico</item>
- <item>Cartella della App</item>
- <item>Cartella Pubblica</item>
- <item>Personalizzata</item>
- </string-array>
- <string name="download_custom_location">Posizione personalizzata dei Download</string>
<string name="font_size_preference">Dimensione font</string>
<string name="open_intents_in_new_tab">Apri gli intenti in una nuova scheda</string>
<string name="open_intents_in_new_tab_summary">Gli intenti sono link inviati da altre app.</string>
<!-- MainWebViewActivity Navigation Menu. -->
<string name="navigation_drawer">Caixa de Navegação</string>
- <string name="navigation">Navegação</string>
<string name="clear_and_exit">Limpar e fechar</string>
<string name="home">Início</string>
<string name="back">Fim</string>
<!-- Save Dialogs. -->
<string name="save_url">Salvar URL</string>
- <string name="save_archive">Salvar Arquivo</string>
<string name="save_text">Salvar Texto</string>
<string name="save_image">Salvar Imagem</string>
<string name="save_logcat">Salvar logcat</string>
<string name="file_name">Nome do Arquivo</string>
- <string name="webpage_mht">Pagina_Web.mht</string>
<string name="webpage_png">Pagina_Web.png</string>
<string name="privacy_browser_logcat_txt">Privacy Browser Logcat.txt</string>
<string name="privacy_browser_version_txt">Privacy Browser Versão.txt</string>
<string name="bytes">bytes</string>
<string name="unknown_size">tamanho desconhecido</string>
<string name="invalid_url">URL inválida</string>
- <string name="ok">OK</string>
<string name="saving_file">Salvando file:</string>
<string name="processing_image">Processando imagem… :</string>
<string name="file_saved">Arquivo Salvo:</string>
<item>OpenPGP</item>
</string-array>
<string name="kitkat_password_encryption_message">A criptografia de senha não funciona no Android KitKat.</string>
- <string name="file_does_not_exist">O arquivo não existe.</string>
- <string name="file_exists_warning">O arquivo já existe. Se você continuar, ele será sobrescrito.</string>
<string name="openkeychain_required">A criptografia OpenPGP requer que o OpenKeychain seja instalado.</string>
<string name="openkeychain_import_instructions">O arquivo não criptografado terá que ser importado em uma etapa separada após ser descriptografado.</string>
<string name="file_location">Localização do Arquivo</string>
<string name="export_successful">Exportação bem sucedida.</string>
<string name="export_failed">A exportação falhou:</string>
<string name="import_failed">A importação falhou:</string>
- <string name="storage_permission">Permissão de armazenamento</string>
- <string name="storage_permission_message">O Privacy Browser precisa de permissão de armazenamento para acessar diretórios públicos.
- Se for negado, os diretórios do aplicativo ainda podem ser usados.</string>
- <string name="storage_permission_explanation">O acesso a arquivos em diretórios públicos requer permissão de armazenamento. Caso contrário, apenas os diretórios de aplicativos funcionarão.</string>
- <string name="cannot_use_location">Este local não pode ser usado porque a permissão de armazenamento não foi concedida.</string>
<!-- Logcat. -->
<string name="copy_string">Cópia</string> <!-- `copy` is a reserved word and should not be used as the name. -->
<string name="clear_cache_summary">Limpa o cache do WebView.</string>
<string name="general">Geral</string>
<string name="homepage">Página inicial</string>
- <string name="download_location">Localização de Download</string>
- <string-array name="download_location_entries">
- <item>Automático</item>
- <item>Diretório do aplicativo</item>
- <item>Diretório público</item>
- <item>Personalizado</item>
- </string-array>
- <string name="download_custom_location">Localização de Download Personalizada</string>
<string name="font_size_preference">Tamanho da Fonte</string>
<string name="open_intents_in_new_tab">Abrir conteúdo em uma nova guia</string>
<string name="open_intents_in_new_tab_summary">Conteúdos são links enviados de outros aplicativos.</string>
<!-- MainWebViewActivity Navigation Menu. -->
<string name="navigation_drawer">Навигационная панель</string>
- <string name="navigation">Навигация</string>
<string name="clear_and_exit">Очистить и выйти</string>
<string name="home">Домой</string>
<string name="back">Назад</string>
<!-- Save Dialogs. -->
<string name="save_url">Сохранить URL</string>
- <string name="save_archive">Сохранить архив</string>
<string name="save_text">Сохранить текст</string>
<string name="save_image">Сохранить изображение</string>
<string name="save_logcat">Сохранить logcat</string>
<string name="file_name">Имя файла</string>
- <string name="webpage_mht">Webpage.mht</string>
<string name="webpage_png">Webpage.png</string>
<string name="privacy_browser_logcat_txt">Privacy Browser Logcat.txt</string>
<string name="privacy_browser_version_txt">Версия Privacy Browser.txt</string>
<string name="bytes">байтов</string>
<string name="unknown_size">неизвестный размер</string>
<string name="invalid_url">неправильный URL</string>
- <string name="ok">OK</string>
<string name="saving_file">Сохранение файла:</string>
<string name="processing_image">Обработка изображения… :</string>
<string name="file_saved">Файл сохранен:</string>
<item>OpenPGP</item>
</string-array>
<string name="kitkat_password_encryption_message">Шифрование паролем не работает на Android KitKat.</string>
- <string name="file_does_not_exist">Файл не существует.</string>
- <string name="file_exists_warning">Файл уже существует. Если вы продолжите, он будет перезаписан.</string>
<string name="openkeychain_required">Для использования шифрования OpenPGP необходимо приложение OpenKeychain.</string>
<string name="openkeychain_import_instructions">Незашифрованный файл должен быть импортирован на отдельном шаге после его дешифрования.</string>
<string name="file_location">Расположение файла</string>
<string name="export_successful">Экспорт выполнен.</string>
<string name="export_failed">Сбой при экспорте:</string>
<string name="import_failed">Сбой при импорте:</string>
- <string name="storage_permission">Доступ к хранилищу</string>
- <string name="storage_permission_message">Privacy Browser необходимо разрешение на доступ к внешним папкам. Если доступ предоставлен не будет, можно использовать локальную папку приложения.</string>
- <string name="storage_permission_explanation">Для доступа к файлам во внешних папках требуется соответствующее разрешение. В противном случае будут работать только локальные папки.</string>
- <string name="cannot_use_location">Это расположение использовано быть не может, поскольку разрешение на хранение предоставлено не было.</string>
<!-- Logcat. -->
<string name="copy_string">Копировать</string> <!-- `copy` is a reserved word and should not be used as the name. -->
<string name="clear_cache_summary">Очищает кэш WebView.</string>
<string name="general">Общее</string>
<string name="homepage">Домашняя страница</string>
- <string name="download_location">Место скачивания</string>
- <string-array name="download_location_entries">
- <item>Авто</item>
- <item>Каталог приложения</item>
- <item>Общий каталог</item>
- <item>Пользовательский</item>
- </string-array>
- <string name="download_custom_location">Загружать в пользовательское расположение</string>
<string name="font_size_preference">Размер шрифта</string>
<string name="open_intents_in_new_tab">Открывать цели в новой вкладке</string>
<string name="open_intents_in_new_tab_summary">Цели - это ссылки, отправленные из других приложений.</string>
<!-- MainWebViewActivity Navigation Menu. -->
<string name="navigation_drawer">Gezinti Menüsü</string>
- <string name="navigation">Gezinti</string>
<string name="clear_and_exit">Temizle ve Çık</string>
<string name="home">Anasayfa</string>
<string name="back">Geri</string>
<string name="webpage_png">Websayfası.png</string>
<string name="unknown_size">Bilinmeyen boyut</string>
<string name="privacy_browser_logcat_txt">Privacy Browser Logcat.txt</string>
- <string name="ok">OK</string>
<!-- View Source. -->
<string name="request_headers">İstek Başlıkları</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:</string>
<string name="import_failed">İçe aktarım başarısız:</string>
- <string name="storage_permission">Depolama İzni</string>
- <string name="storage_permission_message">Privacy Browser, genel dizinlere erişmek için depolama 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 izni gerekmektedir. Aksi takdirde, sadece uygulamanın dizinleri çalışacaktır.</string>
- <string name="cannot_use_location">Depolama izni verilmediği için bu konum kullanılamaz.</string>
<!-- Logcat. -->
<string name="copy_string">Kopyala</string> <!-- `copy` is a reserved word and should not be used as the name. -->
<!-- MainWebViewActivity Navigation Menu. -->
<string name="navigation_drawer">Navigation Drawer</string>
- <string name="navigation">Navigation</string>
<string name="clear_and_exit">Clear and Exit</string>
<string name="home">Home</string>
<string name="back">Back</string>
<!-- Save Dialogs. -->
<string name="save_dialog" translatable="false">Save Dialog</string> <!-- This string is used to tag the save dialog. It is never displayed to the user. -->
<string name="save_url">Save URL</string>
- <string name="save_archive">Save Archive</string>
<string name="save_text">Save Text</string>
<string name="save_image">Save Image</string>
<string name="save_logcat">Save Logcat</string>
<string name="file_name">File name</string>
- <string name="webpage_mht">Webpage.mht</string>
<string name="webpage_png">Webpage.png</string>
<string name="privacy_browser_logcat_txt">Privacy Browser Logcat.txt</string>
<string name="privacy_browser_version_txt">Privacy Browser Version.txt</string>
<string name="bytes">bytes</string>
<string name="unknown_size">unknown size</string>
<string name="invalid_url">invalid URL</string>
- <string name="ok">OK</string>
<string name="saving_file">Saving file:</string>
<string name="processing_image">Processing image… :</string>
<string name="file_saved">File saved:</string>
<item>OpenPGP</item>
</string-array>
<string name="kitkat_password_encryption_message">Password encryption does not work on Android KitKat.</string>
- <string name="file_does_not_exist">The file does not exist.</string>
- <string name="file_exists_warning">The file already exists. If you proceed it will be overwritten.</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="file_location">File Location</string>
<string name="export_successful">Export successful.</string>
<string name="export_failed">Export failed:</string>
<string name="import_failed">Import failed:</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_string">Copy</string> <!-- `copy` is a reserved word and should not be used as the name. -->
<string name="clear_cache_summary">Clears WebView’s cache.</string>
<string name="general">General</string>
<string name="homepage">Homepage</string>
- <string name="download_location">Download location</string>
- <string-array name="download_location_entries">
- <item>Auto</item>
- <item>App directory</item>
- <item>Public directory</item>
- <item>Custom</item>
- </string-array>
- <string-array name="download_location_entry_values" translatable="false"> <!-- None of the items in this string array should be translated. -->
- <item>Auto</item>
- <item>App</item>
- <item>Public</item>
- <item>Custom</item>
- </string-array>
- <string name="download_custom_location">Download custom location</string>
<string name="font_size_preference">Font size</string>
<string name="open_intents_in_new_tab">Open intents in new tab</string>
<string name="open_intents_in_new_tab_summary">Intents are links sent from other apps.</string>
<!-- Non-translatable preference default values. -->
<string name="app_theme_default_value" translatable="false">System default</string>
<string name="custom_user_agent_default_value" translatable="false">PrivacyBrowser/1.0</string>
- <string name="download_custom_location_default_value" translatable="false" />
- <string name="download_location_default_value" translatable="false">Auto</string>
<string name="font_size_default_value" translatable="false">100</string>
<string name="homepage_default_value" translatable="false">https://www.startpage.com/</string>
<string name="proxy_custom_url_default_value" translatable="false">http://localhost:8118</string>
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
+ Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
android:inputType="textUri"
android:icon="?attr/homepageIcon" />
- <ListPreference
- android:key="download_location"
- android:title="@string/download_location"
- android:entries="@array/download_location_entries"
- android:entryValues="@array/download_location_entry_values"
- android:defaultValue="@string/download_location_default_value"
- android:icon="?attr/downloadIcon" />
-
- <EditTextPreference
- android:key="download_custom_location"
- android:title="@string/download_custom_location"
- android:defaultValue="@string/download_custom_location_default_value"
- android:inputType="textUri" />
-
<!-- `android:inputType="number"` currently doesn't work with AndroidX. -->
<EditTextPreference
android:key="font_size"
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.2'
+ classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31"
// NOTE: Do not place your application dependencies here; they belong