From: Soren Stoutner Date: Tue, 20 Nov 2018 19:02:39 +0000 (-0700) Subject: Add password encrypted export. X-Git-Tag: v2.14~3 X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff_plain;h=b4fc354bee486a2df17b3aae6760a3c61eba1e97 Add password encrypted export. --- diff --git a/.idea/dictionaries/soren.xml b/.idea/dictionaries/soren.xml index 55f73139..c96e514b 100644 --- a/.idea/dictionaries/soren.xml +++ b/.idea/dictionaries/soren.xml @@ -30,6 +30,7 @@ checkedtextview chromebooks chromeversion + ciphertext cname commitdiff coordinatorlayout @@ -95,8 +96,10 @@ mozilla navigationview nightmode + nist nojs oname + openpgp orbot panopticlick parameterized @@ -144,6 +147,7 @@ tablayout techrepublic textarea + textinputlayout textview theverge torproject diff --git a/app/src/main/assets/it/guide_bookmarks_dark.html b/app/src/main/assets/it/guide_bookmarks_dark.html index 9604beb0..1aa6f346 100644 --- a/app/src/main/assets/it/guide_bookmarks_dark.html +++ b/app/src/main/assets/it/guide_bookmarks_dark.html @@ -32,8 +32,8 @@ -

Tapping the top floating action button loads the bookmarks activity, which has advanced options like moving and deleting bookmarks. - From the bookmarks activity, there is an option to load the bookmarks database view. - This shows the bookmarks as they exist in the SQLite database, which can be useful for troubleshooting problems with importing and exporting bookmarks.

+

Toccando il pulsante flottante più in alto viene caricata la scheda dei segnalibri, nella quale sono disponibili opzioni avanzate come lo spostamento o l'eliminazione dei segnalibri stessi. + Nella scheda dei segnalibri è anche disponibile un'opzione per caricare la vista del database. + In questo modo è possibile visualizzare i segnalibri come sono salvati nel database SQLite, molto utile nel caso di problemi durante l'importazione o esportazione degli stessi.

\ No newline at end of file diff --git a/app/src/main/assets/it/guide_bookmarks_light.html b/app/src/main/assets/it/guide_bookmarks_light.html index 4bd767a7..29ed4dfd 100644 --- a/app/src/main/assets/it/guide_bookmarks_light.html +++ b/app/src/main/assets/it/guide_bookmarks_light.html @@ -32,8 +32,8 @@ -

Tapping the top floating action button loads the bookmarks activity, which has advanced options like moving and deleting bookmarks. - From the bookmarks activity, there is an option to load the bookmarks database view. - This shows the bookmarks as they exist in the SQLite database, which can be useful for troubleshooting problems with importing and exporting bookmarks.

+

Toccando il pulsante flottante più in alto viene caricata la scheda dei segnalibri, nella quale sono disponibili opzioni avanzate come lo spostamento o l'eliminazione dei segnalibri stessi. + Nella scheda dei segnalibri è anche disponibile un'opzione per caricare la vista del database. + In questo modo è possibile visualizzare i segnalibri come sono salvati nel database SQLite, molto utile nel caso di problemi durante l'importazione o esportazione degli stessi.

\ No newline at end of file diff --git a/app/src/main/assets/it/guide_javascript_dark.html b/app/src/main/assets/it/guide_javascript_dark.html index ababdeed..bc31859b 100644 --- a/app/src/main/assets/it/guide_javascript_dark.html +++ b/app/src/main/assets/it/guide_javascript_dark.html @@ -53,8 +53,9 @@ (JavaScript abilitato). Se si osservano le varie informazioni che webkay può raccogliere con JavaScript abilitato o disabilitato si possono scoprire cose molto interessanti.

-

Browsing the internet with JavaScript disabled, and only enabling it if needed, goes a long way toward protecting privacy. - In addition, JavaScript is used to load much of the annoying advertisements and extra cruft that comes along with most modern websites. - With it disabled, websites will load faster, consume less network traffic, and use less CPU power, which leads to longer battery life.

+

Navigare su internet con JavaScript disabilitato, abilitandolo solo quando necessario, è quindi un passo molto importante per la protezione della propria privacy. + Inoltre JavaScript è utilizzato anche per caricare la maggior parte degli annunci pubblicitari e altra robaccia aggiuntiva presente nei moderni siti web. + Se Javascript viene disabilitato, i siti web saranno caricati più velocemente, riducendo così il traffico sulla rete, e l'utilizzo della CPU sarà ridotto, + risultando così in una maggiore durata della batteria.

\ No newline at end of file diff --git a/app/src/main/assets/it/guide_javascript_light.html b/app/src/main/assets/it/guide_javascript_light.html index 2191caf3..af9b8d9b 100644 --- a/app/src/main/assets/it/guide_javascript_light.html +++ b/app/src/main/assets/it/guide_javascript_light.html @@ -53,8 +53,10 @@ (JavaScript abilitato). Se si osservano le varie informazioni che webkay può raccogliere con JavaScript abilitato o disabilitato si possono scoprire cose molto interessanti.

-

Browsing the internet with JavaScript disabled, and only enabling it if needed, goes a long way toward protecting privacy. - In addition, JavaScript is used to load much of the annoying advertisements and extra cruft that comes along with most modern websites. - With it disabled, websites will load faster, consume less network traffic, and use less CPU power, which leads to longer battery life.

+

Navigare su internet con JavaScript disabilitato, abilitandolo solo quando necessario, è quindi un passo molto importante per la protezione della propria privacy. + Inoltre JavaScript è utilizzato anche per caricare la maggior parte degli annunci pubblicitari e altra robaccia aggiuntiva presente nei moderni siti web. + Se Javascript viene disabilitato, i siti web saranno caricati più velocemente, riducendo così il traffico sulla rete, e l'utilizzo della CPU sarà ridotto, + risultando così in una maggiore durata della batteria.

+ \ No newline at end of file diff --git a/app/src/main/assets/it/guide_local_storage_dark.html b/app/src/main/assets/it/guide_local_storage_dark.html index 320b24a5..85dc0835 100644 --- a/app/src/main/assets/it/guide_local_storage_dark.html +++ b/app/src/main/assets/it/guide_local_storage_dark.html @@ -30,17 +30,18 @@

I cookies proprietari sono definiti dal sito web nella barra della URL all'inizio della pagina.

-

From the early days of the internet, it became obvious that it would be advantageous for websites to be able to store information on a computer for future access. - For example, a website that displays weather information could ask the user for a zip code, and then store it in a cookie. - The next time the user visited the website, weather information would automatically load for that zip code, without the user having to enter it again.

+

Fin dagli albori di internet divenne ovvio che sarebbe stato molto utile per i siti web essere in grado di salvare informazioni sui computer per eventuali accessi successivi. + Ad esempio, un sito web che fornisca informazioni meteo potrebbe chiedere all'utente la sua posizione geografica e salvarla in un cookie. + Nel caso di un accesso successivo al sito web da parte dell'utente, le informazioni meteo sarebbero quindi caricate in automatico per quella posizione geografica, + senza che si renda necessario per l'utente indicarla nuovamente.

-

Like everything else on the web, clever people figured out all types of ways to abuse cookies to do things that users would not approve of if they knew they were happening. - For example, a website can set a cookie with a unique serial number on a device. - Then, every time a user visits the website on that device, it can be linked to a unique profile the server maintains for that serial number, - even if the device connects from different IP addresses.

+

Come per quasi ogni cosa sul web, persone intelligenti hanno ideato moltissimi modi per abusare dei cookies e usarli per finalità che gli utenti non approverebbero, + se solo sapessero cosa sta succedendo. Ad esempio, un sito web può salvare su un dispositivo un cookie con un numero seriale univoco. + In questo modo, ogni volta che l'utente visiterà il sito da quel dispositivo, sarà collegato ad un profilo unico mantenuto sul server per quel particolare numero seriale, + anche se il dispositivo si connette con indirizzo IP diverso.

-

Almost all websites with logins require first-party cookies to be enabled for a user to log in. - That is how they make sure it is still you as you move from page to page on the site, and is, in my opinion, one of the few legitimate uses for cookies.

+

Quasi tutti i che richiedono login hanno bisogno che i cookies proprietari siano abilitati per permettere ad un utente di accedere. + Questo è il modo in cui essi sono sicuri che l'utente sia sempre lui nella navigazione da una pagina all'altra del sito, ed è, a nostro parere, uno dei pochi utilizzi legittimi dei cookies.

Se sono stati abilitati i cookies proprietari ma è stato disabilitato JavaScript, l'icona della privacy sarà gialla con lo scopo di avvertire l'utente.

@@ -69,24 +70,26 @@ Nel corso del tempo le aziende come Facebook (che gestisce anche una rete di annunci) hanno costruito un numero enorme di profili dettagliati di persone che non hanno nemmeno mai creato un account sul loro sito.

-

There is no good reason to ever enable third-party cookies. On devices with Android KitKat or older (version <= 4.4.4 or API <= 20), WebView does not - differentiate - between first-party and third-party cookies. Thus, enabling first-party cookies will also enable third-party cookies.

+

Non esiste nessuna buona ragione di abilitare i cookie di terze parti. Su dispositivi con Android KitKat o precedente (versione <= 4.4.4 o API <= 20), WebView non + fa distinzione + tra cookie proprietari e cookie di terze parti.. Per questo motivo l'abilitazione dei primi permette anche la creazione dei secondi.

DOM Storage

-

Document Object Model storage, also known as web storage, is like cookies on steroids. - Whereas the maximum combined storage size for all cookies from a single URL is 4 kilobytes, - DOM storage can hold megabytes per site. - Because DOM storage uses JavaScript to read and write data, it cannot be enabled unless JavaScript is also enabled.

+

Il Document Object Model storage, conosciuto anche come web storage, è come l'utilizzo di cookie potenziati. + Mentre per tutti i cookie di una singola URL il massimo spazio di memoria occupata è di circa 4 kilobyte, + il DOM storage può occupare alcuni megabyte per sito. + Siccome il DOM storage utilizza JavaScript per leggere e scrivere dati, non può essere abilitato se non viene abilitato anche JavaScript.

Dati dei moduli

-

Form data contains information typed into web forms, like user names, addresses, phone numbers, etc., and lists them in a drop-down box on future visits. - Unlike the other forms of local storage, form data is not sent to the web server without specific user interaction. - Beginning in Android Oreo (8.0), WebView’s form data was replaced by the Autofill service. - As such, controls for form data no longer appear on newer Android devices.

+

I dati dei moduli contengono le informazioni che vengono digitate nei web form, come user name, indirizzi, numeri di telefono, ecc. + per poterli elencare in menù a tendina in caso di visite successive. + A differenza delle altre modalità di memorizzazione locale delle informazioni, i dati dei moduli non vengono inviati ai web server senza una interazione con l'utente. + A partire da Android Oreo (8.0), i dati dei moduli di WebView’s sono stati sostituiti dal + Servizio di Riempimento Automatico. + Per questo motivo i controlli per i dati dei moduli non sono più disponibili nei dispositivi Android più recenti.

\ No newline at end of file diff --git a/app/src/main/assets/it/guide_local_storage_light.html b/app/src/main/assets/it/guide_local_storage_light.html index 9c7859e1..a8d91078 100644 --- a/app/src/main/assets/it/guide_local_storage_light.html +++ b/app/src/main/assets/it/guide_local_storage_light.html @@ -30,17 +30,18 @@

I cookies proprietari sono definiti dal sito web nella barra della URL all'inizio della pagina.

-

From the early days of the internet, it became obvious that it would be advantageous for websites to be able to store information on a computer for future access. - For example, a website that displays weather information could ask the user for a zip code, and then store it in a cookie. - The next time the user visited the website, weather information would automatically load for that zip code, without the user having to enter it again.

+

Fin dagli albori di internet divenne ovvio che sarebbe stato molto utile per i siti web essere in grado di salvare informazioni sui computer per eventuali accessi successivi. + Ad esempio, un sito web che fornisca informazioni meteo potrebbe chiedere all'utente la sua posizione geografica e salvarla in un cookie. + Nel caso di un accesso successivo al sito web da parte dell'utente, le informazioni meteo sarebbero quindi caricate in automatico per quella posizione geografica, + senza che si renda necessario per l'utente indicarla nuovamente.

-

Like everything else on the web, clever people figured out all types of ways to abuse cookies to do things that users would not approve of if they knew they were happening. - For example, a website can set a cookie with a unique serial number on a device. - Then, every time a user visits the website on that device, it can be linked to a unique profile the server maintains for that serial number, - even if the device connects from different IP addresses.

+

Come per quasi ogni cosa sul web, persone intelligenti hanno ideato moltissimi modi per abusare dei cookies e usarli per finalità che gli utenti non approverebbero, + se solo sapessero cosa sta succedendo. Ad esempio, un sito web può salvare su un dispositivo un cookie con un numero seriale univoco. + In questo modo, ogni volta che l'utente visiterà il sito da quel dispositivo, sarà collegato ad un profilo unico mantenuto sul server per quel particolare numero seriale, + anche se il dispositivo si connette con indirizzo IP diverso.

-

Almost all websites with logins require first-party cookies to be enabled for a user to log in. - That is how they make sure it is still you as you move from page to page on the site, and is, in my opinion, one of the few legitimate uses for cookies.

+

Quasi tutti i che richiedono login hanno bisogno che i cookies proprietari siano abilitati per permettere ad un utente di accedere. + Questo è il modo in cui essi sono sicuri che l'utente sia sempre lui nella navigazione da una pagina all'altra del sito, ed è, a nostro parere, uno dei pochi utilizzi legittimi dei cookies.

Se sono stati abilitati i cookies proprietari ma è stato disabilitato JavaScript, l'icona della privacy sarà gialla con lo scopo di avvertire l'utente.

@@ -69,24 +70,26 @@ Nel corso del tempo le aziende come Facebook (che gestisce anche una rete di annunci) hanno costruito un numero enorme di profili dettagliati di persone che non hanno nemmeno mai creato un account sul loro sito.

-

There is no good reason to ever enable third-party cookies. On devices with Android KitKat or older (version <= 4.4.4 or API <= 20), WebView does not - differentiate - between first-party and third-party cookies. Thus, enabling first-party cookies will also enable third-party cookies.

+

Non esiste nessuna buona ragione di abilitare i cookie di terze parti. Su dispositivi con Android KitKat o precedente (versione <= 4.4.4 o API <= 20), WebView non + fa distinzione + tra cookie proprietari e cookie di terze parti.. Per questo motivo l'abilitazione dei primi permette anche la creazione dei secondi.

DOM Storage

-

Document Object Model storage, also known as web storage, is like cookies on steroids. - Whereas the maximum combined storage size for all cookies from a single URL is 4 kilobytes, - DOM storage can hold megabytes per site. - Because DOM storage uses JavaScript to read and write data, it cannot be enabled unless JavaScript is also enabled.

+

Il Document Object Model storage, conosciuto anche come web storage, è come l'utilizzo di cookie potenziati. + Mentre per tutti i cookie di una singola URL il massimo spazio di memoria occupata è di circa 4 kilobyte, + il DOM storage può occupare alcuni megabyte per sito. + Siccome il DOM storage utilizza JavaScript per leggere e scrivere dati, non può essere abilitato se non viene abilitato anche JavaScript.

Dati dei moduli

-

Form data contains information typed into web forms, like user names, addresses, phone numbers, etc., and lists them in a drop-down box on future visits. - Unlike the other forms of local storage, form data is not sent to the web server without specific user interaction. - Beginning in Android Oreo (8.0), WebView’s form data was replaced by the Autofill service. - As such, controls for form data no longer appear on newer Android devices.

+

I dati dei moduli contengono le informazioni che vengono digitate nei web form, come user name, indirizzi, numeri di telefono, ecc. + per poterli elencare in menù a tendina in caso di visite successive. + A differenza delle altre modalità di memorizzazione locale delle informazioni, i dati dei moduli non vengono inviati ai web server senza una interazione con l'utente. + A partire da Android Oreo (8.0), i dati dei moduli di WebView’s sono stati sostituiti dal + Servizio di Riempimento Automatico. + Per questo motivo i controlli per i dati dei moduli non sono più disponibili nei dispositivi Android più recenti.

\ No newline at end of file diff --git a/app/src/main/assets/ru/guide_bookmarks_dark.html b/app/src/main/assets/ru/guide_bookmarks_dark.html index 51adecaf..5ace1b6d 100644 --- a/app/src/main/assets/ru/guide_bookmarks_dark.html +++ b/app/src/main/assets/ru/guide_bookmarks_dark.html @@ -30,8 +30,7 @@ -

Tapping the top floating action button loads the bookmarks activity, which has advanced options like moving and deleting bookmarks. - From the bookmarks activity, there is an option to load the bookmarks database view. - This shows the bookmarks as they exist in the SQLite database, which can be useful for troubleshooting problems with importing and exporting bookmarks.

+

Нажатие верхней всплывающей кнопки позволит совершать над закладками такие действия, как перемещение и удаление. Также из этого меню есть возможность загрузить представление базы данных закладок. + Это отобразит закладки так, как они существуют в базе данных SQLite, что может быть полезным для устранения проблем с импортом и экспортом закладок.

\ No newline at end of file diff --git a/app/src/main/assets/ru/guide_bookmarks_light.html b/app/src/main/assets/ru/guide_bookmarks_light.html index 9e382346..3637b3fc 100644 --- a/app/src/main/assets/ru/guide_bookmarks_light.html +++ b/app/src/main/assets/ru/guide_bookmarks_light.html @@ -30,8 +30,7 @@ -

Tapping the top floating action button loads the bookmarks activity, which has advanced options like moving and deleting bookmarks. - From the bookmarks activity, there is an option to load the bookmarks database view. - This shows the bookmarks as they exist in the SQLite database, which can be useful for troubleshooting problems with importing and exporting bookmarks.

+

Нажатие верхней всплывающей кнопки позволит совершать над закладками такие действия, как перемещение и удаление. Также из этого меню есть возможность загрузить представление базы данных закладок. + Это отобразит закладки так, как они существуют в базе данных SQLite, что может быть полезным для устранения проблем с импортом и экспортом закладок.

\ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java index 620715ac..d8a58972 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java @@ -109,7 +109,7 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements assert appBar != null; // Display the spinner and the back arrow in the app bar. - appBar.setCustomView(R.layout.bookmarks_databaseview_spinner); + appBar.setCustomView(R.layout.spinner); appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_HOME_AS_UP); // Initialize the database handler. `this` specifies the context. The two `null`s do not specify the database name or a `CursorFactory`. The `0` is to specify a database version, but that is set instead using a constant in `BookmarksDatabaseHelper`. @@ -128,7 +128,7 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements MergeCursor foldersMergeCursor = new MergeCursor(new Cursor[]{matrixCursor, foldersCursor}); // Create a resource cursor adapter for the spinner. - ResourceCursorAdapter foldersCursorAdapter = new ResourceCursorAdapter(this, R.layout.bookmarks_databaseview_spinner_item, foldersMergeCursor, 0) { + ResourceCursorAdapter foldersCursorAdapter = new ResourceCursorAdapter(this, R.layout.appbar_spinner_item, foldersMergeCursor, 0) { @Override public void bindView(View view, Context context, Cursor cursor) { // Get a handle for the spinner item text view. @@ -140,10 +140,10 @@ public class BookmarksDatabaseViewActivity extends AppCompatActivity implements }; // Set the resource cursor adapter drop drown view resource. - foldersCursorAdapter.setDropDownViewResource(R.layout.bookmarks_databaseview_spinner_dropdown_item); + foldersCursorAdapter.setDropDownViewResource(R.layout.appbar_spinner_dropdown_item); // Get a handle for the folder spinner and set the adapter. - Spinner folderSpinner = findViewById(R.id.bookmarks_databaseview_spinner); + Spinner folderSpinner = findViewById(R.id.spinner); folderSpinner.setAdapter(foldersCursorAdapter); // Handle clicks on the spinner dropdown. diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java index 26f2e1bc..14f5789f 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java @@ -31,6 +31,7 @@ import android.os.Environment; import android.provider.DocumentsContract; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; +import android.support.design.widget.TextInputLayout; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; @@ -40,8 +41,11 @@ import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; +import android.widget.Spinner; import android.widget.TextView; import com.stoutner.privacybrowser.R; @@ -49,12 +53,27 @@ import com.stoutner.privacybrowser.dialogs.ImportExportStoragePermissionDialog; import com.stoutner.privacybrowser.helpers.ImportExportDatabaseHelper; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; public class ImportExportActivity extends AppCompatActivity implements ImportExportStoragePermissionDialog.ImportExportStoragePermissionDialogListener { - private final static int EXPORT_FILE_PICKER_REQUEST_CODE = 1; - private final static int IMPORT_FILE_PICKER_REQUEST_CODE = 2; - private final static int EXPORT_REQUEST_CODE = 3; - private final static int IMPORT_REQUEST_CODE = 4; + // Create the encryption constants. + private final int NO_ENCRYPTION = 0; + private final int PASSWORD_ENCRYPTION = 1; + private final int GPG_ENCRYPTION = 2; + + // Create the action constants. + private final int IMPORT = 0; + private final int EXPORT = 1; @Override public void onCreate(Bundle savedInstanceState) { @@ -86,18 +105,116 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp appBar.setDisplayHomeAsUpEnabled(true); // Get handles for the views that need to be modified. - EditText exportFileEditText = findViewById(R.id.export_file_edittext); - Button exportButton = findViewById(R.id.export_button); - EditText importFileEditText = findViewById(R.id.import_file_edittext); - Button importButton = findViewById(R.id.import_button); + Spinner encryptionSpinner = findViewById(R.id.encryption_spinner); + TextInputLayout passwordEncryptionTextInputLayout = findViewById(R.id.password_encryption_textinputlayout); + EditText encryptionPasswordEditText = findViewById(R.id.password_encryption_edittext); + Spinner importExportSpinner = findViewById(R.id.import_export_spinner); + EditText fileNameEditText = findViewById(R.id.file_name_edittext); + Button importExportButton = findViewById(R.id.import_export_button); TextView storagePermissionTextView = findViewById(R.id.import_export_storage_permission_textview); - // Initially disable the buttons. - exportButton.setEnabled(false); - importButton.setEnabled(false); + // Create array adapters for the spinners. + ArrayAdapter encryptionArrayAdapter = ArrayAdapter.createFromResource(this, R.array.encryption_type, R.layout.spinner_item); + ArrayAdapter importExportArrayAdapter = ArrayAdapter.createFromResource(this, R.array.import_export_spinner, R.layout.spinner_item); + + // Set the drop down view resource on the spinners. + encryptionArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_items); + importExportArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_items); + + // Set the array adapters for the spinners. + encryptionSpinner.setAdapter(encryptionArrayAdapter); + importExportSpinner.setAdapter(importExportArrayAdapter); + + // Initially hide the encryption layout items. + passwordEncryptionTextInputLayout.setVisibility(View.GONE); + + // Create strings for the default file paths. + String defaultFilePath; + String defaultPasswordEncryptionFilePath; + String defaultGpgEncryptionFilePath; + + // Set the default file paths according to the storage permission status. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. + // Set the default file paths to use the external public directory. + defaultFilePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + getString(R.string.privacy_browser_settings); + defaultPasswordEncryptionFilePath = defaultFilePath + ".aes"; + defaultGpgEncryptionFilePath = defaultFilePath + ".gpg"; + } else { // The storage permission has not been granted. + // Set the default file paths to use the external private directory. + defaultFilePath = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/" + getString(R.string.privacy_browser_settings); + defaultPasswordEncryptionFilePath = defaultFilePath + ".aes"; + defaultGpgEncryptionFilePath = defaultFilePath + ".gpg"; + } - // Enable the export button when the export file EditText isn't empty. - exportFileEditText.addTextChangedListener(new TextWatcher() { + // Set the default file path. + fileNameEditText.setText(defaultFilePath); + + // Display the encryption information when the spinner changes. + encryptionSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + switch (position) { + case NO_ENCRYPTION: + // Hide the encryption layout items. + passwordEncryptionTextInputLayout.setVisibility(View.GONE); + + // Reset the default file path. + fileNameEditText.setText(defaultFilePath); + + // Enable the import/export button if a file name exists. + importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty()); + break; + + case PASSWORD_ENCRYPTION: + // Show the password encryption layout items. + passwordEncryptionTextInputLayout.setVisibility(View.VISIBLE); + + // Update the default file path. + fileNameEditText.setText(defaultPasswordEncryptionFilePath); + + // Enable the import/export button if a file name and password exists. + importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty()); + break; + + case GPG_ENCRYPTION: + // Hide the password encryption layout items. + passwordEncryptionTextInputLayout.setVisibility(View.GONE); + + // Update the default file path. + fileNameEditText.setText(defaultGpgEncryptionFilePath); + break; + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + // Update the import/export button when the spinner changes. + importExportSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + switch (position) { + case IMPORT: + importExportButton.setText(R.string.import_button); + break; + + case EXPORT: + importExportButton.setText(R.string.export); + break; + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + // Update the status of the import/export button when the password changes. + encryptionPasswordEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // Do nothing. @@ -110,12 +227,13 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp @Override public void afterTextChanged(Editable s) { - exportButton.setEnabled(!exportFileEditText.getText().toString().isEmpty()); + // Enable the import/export button if a file name and password exists. + importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty()); } }); - // Enable the import button when the export file EditText isn't empty. - importFileEditText.addTextChangedListener(new TextWatcher() { + // Update the status of the import/export button when the file name EditText changes. + fileNameEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // Do nothing. @@ -128,66 +246,95 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp @Override public void afterTextChanged(Editable s) { - importButton.setEnabled(!importFileEditText.getText().toString().isEmpty()); + // Adjust the export button according to the encryption spinner position. + switch (encryptionSpinner.getSelectedItemPosition()) { + case NO_ENCRYPTION: + // Enable the import/export button if a file name exists. + importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty()); + break; + + case PASSWORD_ENCRYPTION: + // Enable the import/export button if a file name and password exists. + importExportButton.setEnabled(!fileNameEditText.getText().toString().isEmpty() && !encryptionPasswordEditText.getText().toString().isEmpty()); + break; + + case GPG_ENCRYPTION: + break; + } } }); - // Set the initial file paths. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. - // Create a string for the external public path. - String EXTERNAL_PUBLIC_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + getString(R.string.privacy_browser_settings); - - // Set the default path. - exportFileEditText.setText(EXTERNAL_PUBLIC_PATH); - importFileEditText.setText(EXTERNAL_PUBLIC_PATH); - } else { // The storage permission has not been granted. - // Create a string for the external private path. - String EXTERNAL_PRIVATE_PATH = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/" + getString(R.string.privacy_browser_settings); - - // Set the default path. - exportFileEditText.setText(EXTERNAL_PRIVATE_PATH); - importFileEditText.setText(EXTERNAL_PRIVATE_PATH); - } - // Hide the storage permissions TextView on API < 23 as permissions on older devices are automatically granted. if (Build.VERSION.SDK_INT < 23) { storagePermissionTextView.setVisibility(View.GONE); } } - public void exportBrowse(View view) { - // Create the file picker intent. - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + public void browse(View view) { + // Get a handle for the import/export spinner. + Spinner importExportSpinner = findViewById(R.id.import_export_spinner); - // Set the intent MIME type to include all files. - intent.setType("*/*"); + // Check to see if import or export is selected. + if (importExportSpinner.getSelectedItemPosition() == IMPORT) { // Import is selected. + // Create the file picker intent. + Intent importIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - // Set the initial export file name. - intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.privacy_browser_settings)); + // Set the intent MIME type to include all files. + importIntent.setType("*/*"); - // Set the initial directory if API >= 26. - if (Build.VERSION.SDK_INT >= 26) { - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory()); - } + // Set the initial directory if API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + importIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory()); + } + + // Specify that a file that can be opened is requested. + importIntent.addCategory(Intent.CATEGORY_OPENABLE); - // Specify that a file that can be opened is requested. - intent.addCategory(Intent.CATEGORY_OPENABLE); + // Launch the file picker. + startActivityForResult(importIntent, 0); + } else { // Export is selected + // Create the file picker intent. + Intent exportIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - // Launch the file picker. - startActivityForResult(intent, EXPORT_FILE_PICKER_REQUEST_CODE); + // Set the intent MIME type to include all files. + exportIntent.setType("*/*"); + + // Set the initial export file name. + exportIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.privacy_browser_settings)); + + // Set the initial directory if API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + exportIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory()); + } + + // Specify that a file that can be opened is requested. + exportIntent.addCategory(Intent.CATEGORY_OPENABLE); + + // Launch the file picker. + startActivityForResult(exportIntent, 0); + } } - public void onClickExport(View view) { + public void importExport(View view) { + // Get a handle for the import/export spinner. + Spinner importExportSpinner = findViewById(R.id.import_export_spinner); + // Check to see if the storage permission has been granted. if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // Storage permission granted. - // Export the settings. - exportSettings(); + // Check to see if import or export is selected. + if (importExportSpinner.getSelectedItemPosition() == IMPORT) { // Import is selected. + // Import the settings. + importSettings(); + } else { // Export is selected. + // Export the settings. + exportSettings(); + } } else { // Storage permission not granted. - // Get a handle for the export file EditText. - EditText exportFileEditText = findViewById(R.id.export_file_edittext); + // Get a handle for the file name EditText. + EditText fileNameEditText = findViewById(R.id.file_name_edittext); - // Get the export file string. - String exportFileString = exportFileEditText.getText().toString(); + // Get the file name string. + String fileNameString = fileNameEditText.getText().toString(); // Get the external private directory `File`. File externalPrivateDirectoryFile = getApplicationContext().getExternalFilesDir(null); @@ -198,82 +345,61 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp // Get the external private directory string. String externalPrivateDirectory = externalPrivateDirectoryFile.toString(); - // Check to see if the export file path is in the external private directory. - if (exportFileString.startsWith(externalPrivateDirectory)) { // The export path is in the external private directory. - // Export the settings. - exportSettings(); - } else { // The export path is in a public directory. + // 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 (importExportSpinner.getSelectedItemPosition() == IMPORT) { // 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 and set the type to EXPORT_SETTINGS. - DialogFragment importExportStoragePermissionDialogFragment = ImportExportStoragePermissionDialog.type(ImportExportStoragePermissionDialog.EXPORT_SETTINGS); + // Instantiate the storage permission alert dialog. + DialogFragment importExportStoragePermissionDialogFragment = new ImportExportStoragePermissionDialog(); // Show the storage permission alert dialog. The permission will be requested when the dialog is closed. importExportStoragePermissionDialogFragment.show(getFragmentManager(), 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}, EXPORT_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); } } } } - public void importBrowse(View view) { - // Create the file picker intent. - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - - // Set the intent MIME type to include all files. - intent.setType("*/*"); - - // Set the initial directory if API >= 26. - if (Build.VERSION.SDK_INT >= 26) { - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.getExternalStorageDirectory()); - } - - // Specify that a file that can be opened is requested. - intent.addCategory(Intent.CATEGORY_OPENABLE); - - // Launch the file picker. - startActivityForResult(intent, IMPORT_FILE_PICKER_REQUEST_CODE); + @Override + public void onCloseImportExportStoragePermissionDialog() { + // 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); } - public void onClickImport(View view) { - // Check to see if the storage permission has been granted. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // Storage permission granted. - // Import the settings. - importSettings(); - } else { // Storage permission not granted. - // Get a handle for the import file EditText. - EditText importFileEditText = findViewById(R.id.import_file_edittext); - - // Get the import file string. - String importFileString = importFileEditText.getText().toString(); - - // Get the external private directory `File`. - File externalPrivateDirectoryFile = getApplicationContext().getExternalFilesDir(null); - - // Remove the lint error below that `File` might be null. - assert externalPrivateDirectoryFile != null; - - // Get the external private directory string. - String externalPrivateDirectory = externalPrivateDirectoryFile.toString(); + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + // Get a handle for the import/export spinner. + Spinner importExportSpinner = findViewById(R.id.import_export_spinner); - // Check to see if the import file path is in the external private directory. - if (importFileString.startsWith(externalPrivateDirectory)) { // The import path is in the external private directory. + // Check to see if import or export is selected. + if (importExportSpinner.getSelectedItemPosition() == IMPORT) { // Import is selected. + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. // Import the settings. importSettings(); - } else { // The import path is in a public directory. - // Check if the user has previously denied the storage permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the storage permission alert dialog and set the type to IMPORT_SETTINGS. - DialogFragment importExportStoragePermissionDialogFragment = ImportExportStoragePermissionDialog.type(ImportExportStoragePermissionDialog.IMPORT_SETTINGS); - - // Show the storage permission alert dialog. The permission will be requested when the dialog is closed. - importExportStoragePermissionDialogFragment.show(getFragmentManager(), 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.READ_EXTERNAL_STORAGE}, IMPORT_REQUEST_CODE); - } + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(importExportSpinner, getString(R.string.cannot_import), Snackbar.LENGTH_LONG).show(); + } + } else { // Export is selected. + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + // Export the settings. + exportSettings(); + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(importExportSpinner, getString(R.string.cannot_export), Snackbar.LENGTH_LONG).show(); } } } @@ -282,199 +408,326 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp public void onActivityResult(int requestCode, int resultCode, Intent data) { // Don't do anything if the user pressed back from the file picker. if (resultCode == Activity.RESULT_OK) { - // Run the commands for the specific request code. - switch (requestCode) { - case EXPORT_FILE_PICKER_REQUEST_CODE: - // Get a handle for the export file EditText. - EditText exportFileEditText = findViewById(R.id.export_file_edittext); - - // Get the selected export file. - Uri exportUri = data.getData(); - - // Remove the lint warning that the export URI might be null. - assert exportUri != null; - - // Get the raw export path. - String rawExportPath = exportUri.getPath(); - - // Remove the warning that the raw export path might be null. - assert rawExportPath != null; - - // Check to see if the rawExportPath includes a valid storage location. - if (rawExportPath.contains(":")) { // The path is valid. - // Split the path into the initial content uri and the path information. - String exportContentPath = rawExportPath.substring(0, rawExportPath.indexOf(":")); - String exportFilePath = rawExportPath.substring(rawExportPath.indexOf(":") + 1); - - // Create the export path string. - String exportPath; - - // Construct the export path. - switch (exportContentPath) { - // The documents home has a special content path. - case "/document/home": - exportPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + exportFilePath; - break; - - // Everything else for the primary user should be in `/document/primary`. - case "/document/primary": - exportPath = Environment.getExternalStorageDirectory() + "/" + exportFilePath; - break; - - // Just in case, catch everything else and place it in the external storage directory. - default: - exportPath = Environment.getExternalStorageDirectory() + "/" + exportFilePath; - break; - } - - // Set the export file URI as the text for the export file EditText. - exportFileEditText.setText(exportPath); - } else { // The path is invalid. - Snackbar.make(exportFileEditText, rawExportPath + " + " + getString(R.string.invalid_location), Snackbar.LENGTH_INDEFINITE).show(); - } - break; - - case IMPORT_FILE_PICKER_REQUEST_CODE: - // Get a handle for the import file EditText. - EditText importFileEditText = findViewById(R.id.import_file_edittext); - - // Get the selected import file. - Uri importUri = data.getData(); - - // Remove the lint warning that the import URI might be null. - assert importUri != null; - - // Get the raw import path. - String rawImportPath = importUri.getPath(); - - // Remove the warning that the raw import path might be null. - assert rawImportPath != null; - - // Check to see if the rawExportPath includes a valid storage location. - if (rawImportPath.contains(":")) { // The path is valid. - // Split the path into the initial content uri and the path information. - String importContentPath = rawImportPath.substring(0, rawImportPath.indexOf(":")); - String importFilePath = rawImportPath.substring(rawImportPath.indexOf(":") + 1); - - // Create the export path string. - String importPath; - - // Construct the export path. - switch (importContentPath) { - // The documents folder has a special content path. - case "/document/home": - importPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + importFilePath; - break; - - // Everything else for the primary user should be in `/document/primary`. - case "/document/primary": - importPath = Environment.getExternalStorageDirectory() + "/" + importFilePath; - break; - - // Just in case, catch everything else and place it in the external storage directory. - default: - importPath = Environment.getExternalStorageDirectory() + "/" + importFilePath; - break; - } - - // Set the export file URI as the text for the export file EditText. - importFileEditText.setText(importPath); - } else { // The path is invalid. - Snackbar.make(importFileEditText, rawImportPath + " + " + getString(R.string.invalid_location), Snackbar.LENGTH_INDEFINITE).show(); - } - break; + // Get a handle for the file name EditText. + EditText fileNameEditText = findViewById(R.id.file_name_edittext); + + // Get the file name URI. + Uri fileNameUri = data.getData(); + + // Remove the lint warning that the file name URI might be null. + assert fileNameUri != null; + + // Get the raw file name path. + String rawFileNamePath = fileNameUri.getPath(); + + // Remove the warning that the file name path might be null. + assert rawFileNamePath != null; + + // Check to see if the file name Path includes a valid storage location. + if (rawFileNamePath.contains(":")) { // The path is valid. + // Split the path into the initial content uri and the final path information. + String fileNameContentPath = rawFileNamePath.substring(0, rawFileNamePath.indexOf(":")); + String fileNameFinalPath = rawFileNamePath.substring(rawFileNamePath.indexOf(":") + 1); + + // Create the file name path string. + String fileNamePath; + + // Construct the file name path. + switch (fileNameContentPath) { + // The documents home has a special content path. + case "/document/home": + fileNamePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + fileNameFinalPath; + break; + + // Everything else for the primary user should be in `/document/primary`. + case "/document/primary": + fileNamePath = Environment.getExternalStorageDirectory() + "/" + fileNameFinalPath; + break; + + // Just in case, catch everything else and place it in the external storage directory. + default: + fileNamePath = Environment.getExternalStorageDirectory() + "/" + fileNameFinalPath; + break; + } + + // Set the file name path as the text of the file name EditText. + fileNameEditText.setText(fileNamePath); + } else { // The path is invalid. + Snackbar.make(fileNameEditText, rawFileNamePath + " + " + getString(R.string.invalid_location), Snackbar.LENGTH_INDEFINITE).show(); } } } - @Override - public void onCloseImportExportStoragePermissionDialog(int type) { - // Request the storage permission based on the button that was pressed. - switch (type) { - case ImportExportStoragePermissionDialog.EXPORT_SETTINGS: - // Request the storage permission. The export will be run when it finishes. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXPORT_REQUEST_CODE); - 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); - case ImportExportStoragePermissionDialog.IMPORT_SETTINGS: - // Request the storage permission. The import will be run when it finishes. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, IMPORT_REQUEST_CODE); - break; - } - } + // Instantiate the import export database helper. + ImportExportDatabaseHelper importExportDatabaseHelper = new ImportExportDatabaseHelper(); - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - switch (requestCode) { - case EXPORT_REQUEST_CODE: - // 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. - // Export the settings. - exportSettings(); - } else { // The storage permission was not granted. - // Get a handle for the export file EditText. - EditText exportFileEditText = findViewById(R.id.export_file_edittext); + // Get the export file. + File exportFile = new File(fileNameEditText.getText().toString()); - // Display an error snackbar. - Snackbar.make(exportFileEditText, getString(R.string.cannot_export), Snackbar.LENGTH_LONG).show(); - } + // Initialize the export status string. + String exportStatus = ""; + + // Export according to the encryption type. + switch (encryptionSpinner.getSelectedItemPosition()) { + case NO_ENCRYPTION: + // Export the unencrypted file. + exportStatus = importExportDatabaseHelper.exportUnencrypted(exportFile, this); break; - case IMPORT_REQUEST_CODE: - // 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. - // Import the settings. - importSettings(); - } else { // The storage permission was not granted. - // Get a handle for the import file EditText. - EditText importFileEditText = findViewById(R.id.import_file_edittext); + case PASSWORD_ENCRYPTION: + // Use a private temporary export location. + File temporaryUnencryptedExportFile = new File(getApplicationContext().getCacheDir() + "/export.temp"); - // Display an error snackbar. - Snackbar.make(importFileEditText, getString(R.string.cannot_import), Snackbar.LENGTH_LONG).show(); - } - break; - } - } + // Create an unencrypted export in the private location. + exportStatus = importExportDatabaseHelper.exportUnencrypted(temporaryUnencryptedExportFile, this); - private void exportSettings() { - // Get a handle for the export file EditText. - EditText exportFileEditText = findViewById(R.id.export_file_edittext); + try { + // Create an unencrypted export file input stream. + FileInputStream unencryptedExportFileInputStream = new FileInputStream(temporaryUnencryptedExportFile); - // Get the export file string. - String exportFileString = exportFileEditText.getText().toString(); + // Delete the encrypted export file if it exists. + if (exportFile.exists()) { + //noinspection ResultOfMethodCallIgnored + exportFile.delete(); + } - // Set the export file. - File exportFile = new File(exportFileString); + // Create an encrypted export file output stream. + FileOutputStream encryptedExportFileOutputStream = new FileOutputStream(exportFile); - // Instantiate the import export database helper. - ImportExportDatabaseHelper importExportDatabaseHelper = new ImportExportDatabaseHelper(); + // Get a handle for the encryption password EditText. + EditText encryptionPasswordEditText = findViewById(R.id.password_encryption_edittext); + + // Get the encryption password. + String encryptionPasswordString = encryptionPasswordEditText.getText().toString(); + + // Initialize a secure random number generator. + SecureRandom secureRandom = new SecureRandom(); + + // Get a 256 bit (32 byte) random salt. + byte[] saltByteArray = new byte[32]; + secureRandom.nextBytes(saltByteArray); + + // Convert the encryption password to a byte array. + byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes("UTF-8"); + + // 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 SHA-512 message digest. + MessageDigest messageDigest = MessageDigest.getInstance("SHA-512"); + + // Hash the salted encryption password. Otherwise, any characters after the 32nd character in the password are ignored. + byte[] hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray); + + // Truncate the encryption password byte array to 256 bits (32 bytes). + byte[] truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32); + + // Create an AES secret key from the encryption password byte array. + SecretKeySpec secretKey = new SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES"); + + // 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"); + + // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector. + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector); + + // 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); - // Export the unencrypted file. - String exportStatus = importExportDatabaseHelper.exportUnencrypted(exportFile, getApplicationContext()); + // 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]; + + // 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(); + 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(); + } + break; + + case GPG_ENCRYPTION: + + break; + } // Show a disposition snackbar. if (exportStatus.equals(ImportExportDatabaseHelper.EXPORT_SUCCESSFUL)) { - Snackbar.make(exportFileEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show(); + Snackbar.make(fileNameEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show(); } else { - Snackbar.make(exportFileEditText, getString(R.string.export_failed) + " " + exportStatus, Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(fileNameEditText, getString(R.string.export_failed) + " " + exportStatus, Snackbar.LENGTH_INDEFINITE).show(); } } private void importSettings() { - // Get a handle for the import file EditText. - EditText importFileEditText = findViewById(R.id.import_file_edittext); - - // Get the import file string. - String importFileString = importFileEditText.getText().toString(); - - // Set the import file. - File importFile = new File(importFileString); + // Get a handle for the views. + Spinner encryptionSpinner = findViewById(R.id.encryption_spinner); + EditText fileNameEditText = findViewById(R.id.file_name_edittext); // Instantiate the import export database helper. ImportExportDatabaseHelper importExportDatabaseHelper = new ImportExportDatabaseHelper(); - // Import the unencrypted file. - String importStatus = importExportDatabaseHelper.importUnencrypted(importFile, getApplicationContext()); + // Get the import file. + File importFile = new File(fileNameEditText.getText().toString()); + + // Initialize the import status string + String importStatus = ""; + + // Import according to the encryption type. + switch (encryptionSpinner.getSelectedItemPosition()) { + case NO_ENCRYPTION: + // Import the unencrypted file. + importStatus = importExportDatabaseHelper.importUnencrypted(importFile, this); + break; + + case PASSWORD_ENCRYPTION: + // Use a private temporary import location. + File temporaryUnencryptedImportFile = new File(getApplicationContext().getCacheDir() + "/import.temp"); + + try { + // Create an encrypted import file input stream. + FileInputStream encryptedImportFileInputStream = new FileInputStream(importFile); + + // Delete the temporary import file if it exists. + if (temporaryUnencryptedImportFile.exists()) { + //noinspection ResultOfMethodCallIgnored + temporaryUnencryptedImportFile.delete(); + } + + // Create an unencrypted import file output stream. + FileOutputStream unencryptedImportFileOutputStream = new FileOutputStream(temporaryUnencryptedImportFile); + + // Get a handle for the encryption password EditText. + EditText encryptionPasswordEditText = findViewById(R.id.password_encryption_edittext); + + // Get the encryption password. + String encryptionPasswordString = encryptionPasswordEditText.getText().toString(); + + // Get the salt from the beginning of the import file. + byte[] saltByteArray = new byte[32]; + //noinspection ResultOfMethodCallIgnored + encryptedImportFileInputStream.read(saltByteArray); + + // Get the initialization vector from the import file. + byte[] initializationVector = new byte[12]; + //noinspection ResultOfMethodCallIgnored + encryptedImportFileInputStream.read(initializationVector); + + // Convert the encryption password to a byte array. + byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes("UTF-8"); + + // 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 SHA-512 message digest. + MessageDigest messageDigest = MessageDigest.getInstance("SHA-512"); + + // Hash the salted encryption password. Otherwise, any characters after the 32nd character in the password are ignored. + byte[] hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray); + + // Truncate the encryption password byte array to 256 bits (32 bytes). + byte[] truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32); + + // Create an AES secret key from the encryption password byte array. + SecretKeySpec secretKey = new SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES"); + + // 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"); + + // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector. + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector); + + // Initialize the cipher. + cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); + + // Create a cipher input stream. + CipherInputStream cipherInputStream = new CipherInputStream(encryptedImportFileInputStream, cipher); + + // 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]; + + // 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); + } + + // 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; + + case GPG_ENCRYPTION: + + break; + } // Respond to the import disposition. if (importStatus.equals(ImportExportDatabaseHelper.IMPORT_SUCCESSFUL)) { // The import was successful. @@ -491,7 +744,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp startActivity(restartIntent); } else { // The import was not successful. // Display a snack bar with the import error. - Snackbar.make(importFileEditText, getString(R.string.import_failed) + " " + importStatus, Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(fileNameEditText, getString(R.string.import_failed) + " " + importStatus, Snackbar.LENGTH_INDEFINITE).show(); } } } \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 5e4a21d9..d3f0d016 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -1247,7 +1247,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Initialize the user agent array adapter and string array. - userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.domain_settings_spinner_item); + userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item); userAgentDataArray = getResources().getStringArray(R.array.user_agent_data); // Apply the app settings from the shared preferences. diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java index a62708a3..71ce3070 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java @@ -79,7 +79,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi assert appBar != null; // Display the spinner and the back arrow in the app bar. - appBar.setCustomView(R.layout.requests_spinner); + appBar.setCustomView(R.layout.spinner); appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_HOME_AS_UP); // Initialize the resource array lists. A list is needed for all the resource requests, or the activity can crash if `MainWebViewActivity.resourceRequests` is modified after the activity loads. @@ -137,7 +137,7 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi spinnerCursor.addRow(new Object[]{4, getString(R.string.blocked_plural) + " - " + blockedResourceRequests.size()}); // Create a resource cursor adapter for the spinner. - ResourceCursorAdapter spinnerCursorAdapter = new ResourceCursorAdapter(this, R.layout.requests_spinner_item, spinnerCursor, 0) { + ResourceCursorAdapter spinnerCursorAdapter = new ResourceCursorAdapter(this, R.layout.appbar_spinner_item, spinnerCursor, 0) { @Override public void bindView(View view, Context context, Cursor cursor) { // Get a handle for the spinner item text view. @@ -149,10 +149,10 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi }; // Set the resource cursor adapter drop down view resource. - spinnerCursorAdapter.setDropDownViewResource(R.layout.requests_spinner_dropdown_item); + spinnerCursorAdapter.setDropDownViewResource(R.layout.appbar_spinner_dropdown_item); // Get a handle for the app bar spinner and set the adapter. - Spinner appBarSpinner = findViewById(R.id.requests_spinner); + Spinner appBarSpinner = findViewById(R.id.spinner); appBarSpinner.setAdapter(spinnerCursorAdapter); // Handle clicks on the spinner dropdown. diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.java index e24c893b..272e4719 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.java @@ -216,7 +216,7 @@ public class EditBookmarkDatabaseViewDialog extends AppCompatDialogFragment { assert getContext() != null; // Create a `ResourceCursorAdapter` for the `Spinner`. `0` specifies no flags.; - ResourceCursorAdapter foldersCursorAdapter = new ResourceCursorAdapter(getContext(), R.layout.edit_bookmark_databaseview_spinner_item, foldersMergeCursor, 0) { + ResourceCursorAdapter foldersCursorAdapter = new ResourceCursorAdapter(getContext(), R.layout.spinner_item, foldersMergeCursor, 0) { @Override public void bindView(View view, Context context, Cursor cursor) { // Get a handle for the `Spinner` item `TextView`. @@ -228,7 +228,7 @@ public class EditBookmarkDatabaseViewDialog extends AppCompatDialogFragment { }; // Set the `ResourceCursorAdapter` drop drown view resource. - foldersCursorAdapter.setDropDownViewResource(R.layout.edit_bookmark_databaseview_spinner_dropdown_item); + foldersCursorAdapter.setDropDownViewResource(R.layout.spinner_dropdown_items); // Set the adapter for the folder `Spinner`. folderSpinner.setAdapter(foldersCursorAdapter); diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.java index 0fb537b8..c2d44cd0 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.java @@ -219,7 +219,7 @@ public class EditBookmarkFolderDatabaseViewDialog extends AppCompatDialogFragmen assert getContext() != null; // Create a `ResourceCursorAdapter` for the `Spinner`. `0` specifies no flags.; - ResourceCursorAdapter foldersCursorAdapter = new ResourceCursorAdapter(getContext(), R.layout.edit_bookmark_databaseview_spinner_item, foldersMergeCursor, 0) { + ResourceCursorAdapter foldersCursorAdapter = new ResourceCursorAdapter(getContext(), R.layout.spinner_item, foldersMergeCursor, 0) { @Override public void bindView(View view, Context context, Cursor cursor) { // Get a handle for the `Spinner` item `TextView`. @@ -231,7 +231,7 @@ public class EditBookmarkFolderDatabaseViewDialog extends AppCompatDialogFragmen }; // Set the `ResourceCursorAdapter` drop drown view resource. - foldersCursorAdapter.setDropDownViewResource(R.layout.edit_bookmark_databaseview_spinner_dropdown_item); + foldersCursorAdapter.setDropDownViewResource(R.layout.spinner_dropdown_items); // Set the adapter for the folder `Spinner`. folderSpinner.setAdapter(foldersCursorAdapter); diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java index d5b93900..447b67e6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ImportExportStoragePermissionDialog.java @@ -31,16 +31,12 @@ import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.activities.MainWebViewActivity; public class ImportExportStoragePermissionDialog extends DialogFragment { - // The constants are used to differentiate between the two commands. - public static final int EXPORT_SETTINGS = 1; - public static final int IMPORT_SETTINGS = 2; - // The listener is used in `onAttach()` and `onCreateDialog()`. private ImportExportStoragePermissionDialogListener importExportStoragePermissionDialogListener; // The public interface is used to send information back to the parent activity. public interface ImportExportStoragePermissionDialogListener { - void onCloseImportExportStoragePermissionDialog(int type); + void onCloseImportExportStoragePermissionDialog(); } @Override @@ -52,24 +48,8 @@ public class ImportExportStoragePermissionDialog extends DialogFragment { importExportStoragePermissionDialogListener = (ImportExportStoragePermissionDialogListener) context; } - public static ImportExportStoragePermissionDialog type(int type) { - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Store the download type in the bundle. - argumentsBundle.putInt("type", type); - - // Add the arguments bundle to this instance of the dialog. - ImportExportStoragePermissionDialog thisImportExportStoragePermissionDialog = new ImportExportStoragePermissionDialog(); - thisImportExportStoragePermissionDialog.setArguments(argumentsBundle); - return thisImportExportStoragePermissionDialog; - } - @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - // Store the download type in a local variable. - int type = getArguments().getInt("type"); - // Use a builder to create the alert dialog. AlertDialog.Builder dialogBuilder; @@ -91,7 +71,7 @@ public class ImportExportStoragePermissionDialog extends DialogFragment { // Set an `onClick` listener on the negative button. dialogBuilder.setNegativeButton(R.string.ok, (DialogInterface dialog, int which) -> { // Inform the parent activity that the dialog was closed. - importExportStoragePermissionDialogListener.onCloseImportExportStoragePermissionDialog(type); + importExportStoragePermissionDialogListener.onCloseImportExportStoragePermissionDialog(); }); // Create an alert dialog from the builder. diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java index bfa6da74..76dacba6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java @@ -218,29 +218,29 @@ public class DomainSettingsFragment extends Fragment { savedSslCertificateEndDate = new Date(domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); } - // Create `ArrayAdapters` for the `Spinners`and their `entry values`. - ArrayAdapter translatedUserAgentArrayAdapter = ArrayAdapter.createFromResource(context, R.array.translated_domain_settings_user_agent_names, R.layout.domain_settings_spinner_item); - ArrayAdapter fontSizeArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_font_size_entries, R.layout.domain_settings_spinner_item); - ArrayAdapter fontSizeEntryValuesArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_font_size_entry_values, R.layout.domain_settings_spinner_item); - ArrayAdapter swipeToRefreshArrayAdapter = ArrayAdapter.createFromResource(context, R.array.swipe_to_refresh_array, R.layout.domain_settings_spinner_item); - ArrayAdapter nightModeArrayAdapter = ArrayAdapter.createFromResource(context, R.array.night_mode_array, R.layout.domain_settings_spinner_item); - ArrayAdapter displayImagesArrayAdapter = ArrayAdapter.createFromResource(context, R.array.display_webpage_images_array, R.layout.domain_settings_spinner_item); - - // Set the `DropDownViewResource` on the `Spinners`. + // Create array adapters for the spinners. + ArrayAdapter translatedUserAgentArrayAdapter = ArrayAdapter.createFromResource(context, R.array.translated_domain_settings_user_agent_names, R.layout.spinner_item); + ArrayAdapter fontSizeArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_font_size_entries, R.layout.spinner_item); + ArrayAdapter fontSizeEntryValuesArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_font_size_entry_values, R.layout.spinner_item); + ArrayAdapter swipeToRefreshArrayAdapter = ArrayAdapter.createFromResource(context, R.array.swipe_to_refresh_array, R.layout.spinner_item); + ArrayAdapter nightModeArrayAdapter = ArrayAdapter.createFromResource(context, R.array.night_mode_array, R.layout.spinner_item); + ArrayAdapter displayImagesArrayAdapter = ArrayAdapter.createFromResource(context, R.array.display_webpage_images_array, R.layout.spinner_item); + + // Set the drop down view resource on the spinners. translatedUserAgentArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); fontSizeArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); swipeToRefreshArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); nightModeArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); displayImagesArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items); - // Set the `ArrayAdapters` for the `Spinners`. + // Set the array adapters for the spinners. userAgentSpinner.setAdapter(translatedUserAgentArrayAdapter); fontSizeSpinner.setAdapter(fontSizeArrayAdapter); swipeToRefreshSpinner.setAdapter(swipeToRefreshArrayAdapter); nightModeSpinner.setAdapter(nightModeArrayAdapter); displayWebpageImagesSpinner.setAdapter(displayImagesArrayAdapter); - // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. + // Create a spannable string builder for each TextView that needs multiple colors of text. SpannableStringBuilder savedSslCertificateIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslCertificateIssuedToCNameString); SpannableStringBuilder savedSslCertificateIssuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + savedSslCertificateIssuedToONameString); SpannableStringBuilder savedSslCertificateIssuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + savedSslCertificateIssuedToUNameString); @@ -648,7 +648,7 @@ public class DomainSettingsFragment extends Fragment { final String webViewDefaultUserAgentString = bareWebView.getSettings().getUserAgentString(); // Get a handle for the user agent array adapter. This array does not contain the `System default` entry. - ArrayAdapter userAgentNamesArray = ArrayAdapter.createFromResource(context, R.array.user_agent_names, R.layout.domain_settings_spinner_item); + ArrayAdapter userAgentNamesArray = ArrayAdapter.createFromResource(context, R.array.user_agent_names, R.layout.spinner_item); // Get the positions of the user agent and the default user agent. int userAgentArrayPosition = userAgentNamesArray.getPosition(currentUserAgentName); diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java index 7966ef7b..65b18bae 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java @@ -141,7 +141,7 @@ public class SettingsFragment extends PreferenceFragment { final WebView bareWebView = bareWebViewLayout.findViewById(R.id.bare_webview); // Get the user agent arrays. - ArrayAdapter userAgentNamesArray = ArrayAdapter.createFromResource(context, R.array.user_agent_names, R.layout.domain_settings_spinner_item); + ArrayAdapter userAgentNamesArray = ArrayAdapter.createFromResource(context, R.array.user_agent_names, R.layout.spinner_item); String[] translatedUserAgentNamesArray = getResources().getStringArray(R.array.translated_user_agent_names); String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data); diff --git a/app/src/main/res/color/appbar_spinner_color_selector_dark.xml b/app/src/main/res/color/appbar_spinner_color_selector_dark.xml new file mode 100644 index 00000000..f58c7830 --- /dev/null +++ b/app/src/main/res/color/appbar_spinner_color_selector_dark.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/appbar_spinner_color_selector_light.xml b/app/src/main/res/color/appbar_spinner_color_selector_light.xml new file mode 100644 index 00000000..03ac9ea0 --- /dev/null +++ b/app/src/main/res/color/appbar_spinner_color_selector_light.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/bookmarks_spinner_color_selector_dark.xml b/app/src/main/res/color/bookmarks_spinner_color_selector_dark.xml deleted file mode 100644 index bec0b3a9..00000000 --- a/app/src/main/res/color/bookmarks_spinner_color_selector_dark.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/bookmarks_spinner_color_selector_light.xml b/app/src/main/res/color/bookmarks_spinner_color_selector_light.xml deleted file mode 100644 index 3e8f830e..00000000 --- a/app/src/main/res/color/bookmarks_spinner_color_selector_light.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/button_background_color_selector_dark.xml b/app/src/main/res/color/button_background_color_selector_dark.xml new file mode 100644 index 00000000..85b04231 --- /dev/null +++ b/app/src/main/res/color/button_background_color_selector_dark.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/button_background_color_selector_light.xml b/app/src/main/res/color/button_background_color_selector_light.xml new file mode 100644 index 00000000..8049c091 --- /dev/null +++ b/app/src/main/res/color/button_background_color_selector_light.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/button_text_color_selector_dark.xml b/app/src/main/res/color/button_text_color_selector_dark.xml new file mode 100644 index 00000000..090ae067 --- /dev/null +++ b/app/src/main/res/color/button_text_color_selector_dark.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/button_text_color_selector_light.xml b/app/src/main/res/color/button_text_color_selector_light.xml new file mode 100644 index 00000000..74c6c5bf --- /dev/null +++ b/app/src/main/res/color/button_text_color_selector_light.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/edit_bookmark_spinner_color_selector_dark.xml b/app/src/main/res/color/edit_bookmark_spinner_color_selector_dark.xml deleted file mode 100644 index 51751824..00000000 --- a/app/src/main/res/color/edit_bookmark_spinner_color_selector_dark.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/edit_bookmark_spinner_color_selector_light.xml b/app/src/main/res/color/edit_bookmark_spinner_color_selector_light.xml deleted file mode 100644 index ef42d28b..00000000 --- a/app/src/main/res/color/edit_bookmark_spinner_color_selector_light.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/requests_spinner_color_selector_dark.xml b/app/src/main/res/color/requests_spinner_color_selector_dark.xml deleted file mode 100644 index 06e0fa42..00000000 --- a/app/src/main/res/color/requests_spinner_color_selector_dark.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/requests_spinner_color_selector_light.xml b/app/src/main/res/color/requests_spinner_color_selector_light.xml deleted file mode 100644 index 3e8f830e..00000000 --- a/app/src/main/res/color/requests_spinner_color_selector_light.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/spinner_color_selector_dark.xml b/app/src/main/res/color/spinner_color_selector_dark.xml new file mode 100644 index 00000000..0a683dbc --- /dev/null +++ b/app/src/main/res/color/spinner_color_selector_dark.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/spinner_color_selector_light.xml b/app/src/main/res/color/spinner_color_selector_light.xml new file mode 100644 index 00000000..89204238 --- /dev/null +++ b/app/src/main/res/color/spinner_color_selector_light.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/appbar_spinner_dropdown_item.xml b/app/src/main/res/layout/appbar_spinner_dropdown_item.xml new file mode 100644 index 00000000..d71b180d --- /dev/null +++ b/app/src/main/res/layout/appbar_spinner_dropdown_item.xml @@ -0,0 +1,35 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/appbar_spinner_item.xml b/app/src/main/res/layout/appbar_spinner_item.xml new file mode 100644 index 00000000..3e7cc3c3 --- /dev/null +++ b/app/src/main/res/layout/appbar_spinner_item.xml @@ -0,0 +1,31 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bookmarks_databaseview_spinner.xml b/app/src/main/res/layout/bookmarks_databaseview_spinner.xml deleted file mode 100644 index 9750d591..00000000 --- a/app/src/main/res/layout/bookmarks_databaseview_spinner.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout/bookmarks_databaseview_spinner_dropdown_item.xml b/app/src/main/res/layout/bookmarks_databaseview_spinner_dropdown_item.xml deleted file mode 100644 index 57860eea..00000000 --- a/app/src/main/res/layout/bookmarks_databaseview_spinner_dropdown_item.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/bookmarks_databaseview_spinner_item.xml b/app/src/main/res/layout/bookmarks_databaseview_spinner_item.xml deleted file mode 100644 index 62cbb1d0..00000000 --- a/app/src/main/res/layout/bookmarks_databaseview_spinner_item.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/domain_settings_fragment.xml b/app/src/main/res/layout/domain_settings_fragment.xml index d71868b2..df08c91c 100644 --- a/app/src/main/res/layout/domain_settings_fragment.xml +++ b/app/src/main/res/layout/domain_settings_fragment.xml @@ -417,7 +417,6 @@ android:layout_marginEnd="36dp" android:textSize="13sp" /> - + android:importantForAutofill="no" + tools:targetApi="26" /> diff --git a/app/src/main/res/layout/domain_settings_spinner_item.xml b/app/src/main/res/layout/domain_settings_spinner_item.xml deleted file mode 100644 index 0896cffc..00000000 --- a/app/src/main/res/layout/domain_settings_spinner_item.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/edit_bookmark_databaseview_dialog.xml b/app/src/main/res/layout/edit_bookmark_databaseview_dialog.xml index fa7ee1de..f690f9c8 100644 --- a/app/src/main/res/layout/edit_bookmark_databaseview_dialog.xml +++ b/app/src/main/res/layout/edit_bookmark_databaseview_dialog.xml @@ -1,7 +1,7 @@ - + android:selectAllOnFocus="true" + android:importantForAutofill="no" + tools:targetApi="26" /> \ No newline at end of file diff --git a/app/src/main/res/layout/edit_bookmark_databaseview_spinner_dropdown_item.xml b/app/src/main/res/layout/edit_bookmark_databaseview_spinner_dropdown_item.xml deleted file mode 100644 index af46e36f..00000000 --- a/app/src/main/res/layout/edit_bookmark_databaseview_spinner_dropdown_item.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/edit_bookmark_databaseview_spinner_item.xml b/app/src/main/res/layout/edit_bookmark_databaseview_spinner_item.xml deleted file mode 100644 index 0896cffc..00000000 --- a/app/src/main/res/layout/edit_bookmark_databaseview_spinner_item.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/edit_bookmark_folder_databaseview_dialog.xml b/app/src/main/res/layout/edit_bookmark_folder_databaseview_dialog.xml index 6ea35eeb..4b9cec3d 100644 --- a/app/src/main/res/layout/edit_bookmark_folder_databaseview_dialog.xml +++ b/app/src/main/res/layout/edit_bookmark_folder_databaseview_dialog.xml @@ -1,7 +1,7 @@ + + android:textColor="?colorAccent" /> - - + + + + app:passwordToggleEnabled="true" > - - - - - - - -