Merge branch 'master' into AuthenticationActivity-is-missing-secure-flag-and-can-be-screencaptured-#378

This commit is contained in:
Marc Schwede 2019-09-19 09:43:12 +02:00
commit d6edb612e5
34 changed files with 464 additions and 192 deletions

View file

@ -54,8 +54,9 @@
android:theme="@style/AppTheme.NoActionBar" /> android:theme="@style/AppTheme.NoActionBar" />
<activity <activity
android:name="com.journeyapps.barcodescanner.CaptureActivity" android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:hardwareAccelerated="true"
android:screenOrientation="fullSensor" android:screenOrientation="fullSensor"
tools:replace="screenOrientation" /> tools:replace="screenOrientation" />
<activity android:name=".Activities.SecureCaptureActivity" /> <activity android:name=".Activities.SecureCaptureActivity" />
<activity <activity
android:name=".Activities.PanicResponderActivity" android:name=".Activities.PanicResponderActivity"

View file

@ -36,7 +36,6 @@ import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewStub; import android.view.ViewStub;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@ -51,17 +50,20 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.Dialogs.PasswordEntryDialog; import org.shadowice.flocke.andotp.Dialogs.PasswordEntryDialog;
import org.shadowice.flocke.andotp.R; import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.BackupHelper;
import org.shadowice.flocke.andotp.Utilities.Constants; import org.shadowice.flocke.andotp.Utilities.Constants;
import org.shadowice.flocke.andotp.Utilities.DatabaseHelper; import org.shadowice.flocke.andotp.Utilities.DatabaseHelper;
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper; import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
import org.shadowice.flocke.andotp.Utilities.FileHelper; import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
import org.shadowice.flocke.andotp.Utilities.Tools; import org.shadowice.flocke.andotp.Utilities.Tools;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -120,6 +122,7 @@ public class BackupActivity extends BaseActivity {
TextView cryptSetup = v.findViewById(R.id.msg_crypt_setup); TextView cryptSetup = v.findViewById(R.id.msg_crypt_setup);
LinearLayout backupCrypt = v.findViewById(R.id.button_backup_crypt); LinearLayout backupCrypt = v.findViewById(R.id.button_backup_crypt);
LinearLayout restoreCrypt = v.findViewById(R.id.button_restore_crypt); LinearLayout restoreCrypt = v.findViewById(R.id.button_restore_crypt);
LinearLayout restoreCryptOld = v.findViewById(R.id.button_restore_crypt_old);
if (settings.getBackupPasswordEnc().isEmpty()) { if (settings.getBackupPasswordEnc().isEmpty()) {
cryptSetup.setVisibility(View.VISIBLE); cryptSetup.setVisibility(View.VISIBLE);
@ -141,6 +144,13 @@ public class BackupActivity extends BaseActivity {
} }
}); });
restoreCryptOld.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openFileWithPermissions(Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD, Constants.PERMISSIONS_BACKUP_READ_IMPORT_CRYPT_OLD);
}
});
// OpenPGP // OpenPGP
String PGPProvider = settings.getOpenPGPProvider(); String PGPProvider = settings.getOpenPGPProvider();
@ -179,6 +189,23 @@ public class BackupActivity extends BaseActivity {
replace = v.findViewById(R.id.backup_replace); replace = v.findViewById(R.id.backup_replace);
if (! settings.getNewBackupFormatDialogShown()) {
showNewBackupInfo();
}
}
private void showNewBackupInfo() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.backup_new_format_dialog_title)
.setMessage(R.string.backup_new_format_dialog_msg)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
settings.setNewBackupFormatDialogShown(true);
}
})
.create()
.show();
} }
// End with a result // End with a result
@ -212,7 +239,7 @@ public class BackupActivity extends BaseActivity {
// Get the result from permission requests // Get the result from permission requests
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == Constants.PERMISSIONS_BACKUP_READ_IMPORT_PLAIN) { if (requestCode == Constants.PERMISSIONS_BACKUP_READ_IMPORT_PLAIN) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_PLAIN); showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_PLAIN);
@ -231,6 +258,12 @@ public class BackupActivity extends BaseActivity {
} else { } else {
Toast.makeText(this, R.string.backup_toast_storage_permissions, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_storage_permissions, Toast.LENGTH_LONG).show();
} }
} else if (requestCode == Constants.PERMISSIONS_BACKUP_READ_IMPORT_CRYPT_OLD) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD);
} else {
Toast.makeText(this, R.string.backup_toast_storage_permissions, Toast.LENGTH_LONG).show();
}
} else if (requestCode == Constants.PERMISSIONS_BACKUP_WRITE_EXPORT_CRYPT) { } else if (requestCode == Constants.PERMISSIONS_BACKUP_WRITE_EXPORT_CRYPT) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showSaveFileSelector(Constants.BACKUP_MIMETYPE_CRYPT, Constants.BackupType.ENCRYPTED, Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT); showSaveFileSelector(Constants.BACKUP_MIMETYPE_CRYPT, Constants.BackupType.ENCRYPTED, Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT);
@ -269,7 +302,11 @@ public class BackupActivity extends BaseActivity {
} }
} else if (requestCode == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT && resultCode == RESULT_OK) { } else if (requestCode == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT && resultCode == RESULT_OK) {
if (intent != null) { if (intent != null) {
doRestoreCrypt(intent.getData()); doRestoreCrypt(intent.getData(), false);
}
} else if (requestCode == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD && resultCode == RESULT_OK) {
if (intent != null) {
doRestoreCrypt(intent.getData(), true);
} }
} else if (requestCode == Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT && resultCode == RESULT_OK) { } else if (requestCode == Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT && resultCode == RESULT_OK) {
if (intent != null) { if (intent != null) {
@ -300,7 +337,9 @@ public class BackupActivity extends BaseActivity {
if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_PLAIN) if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_PLAIN)
doRestorePlain(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_PLAIN)); doRestorePlain(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_PLAIN));
else if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT) else if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT)
doRestoreCrypt(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_CRYPT)); doRestoreCrypt(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_CRYPT), false);
else if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD)
doRestoreCrypt(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_CRYPT), true);
else if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_PGP) else if (intentId == Constants.INTENT_BACKUP_OPEN_DOCUMENT_PGP)
restoreEncryptedWithPGP(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_PGP), null); restoreEncryptedWithPGP(Tools.buildUri(settings.getBackupDir(), Constants.BACKUP_FILENAME_PGP), null);
} }
@ -311,16 +350,16 @@ public class BackupActivity extends BaseActivity {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(mimeType); intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_TITLE, FileHelper.backupFilename(this, backupType)); intent.putExtra(Intent.EXTRA_TITLE, BackupHelper.backupFilename(this, backupType));
startActivityForResult(intent, intentId); startActivityForResult(intent, intentId);
} else { } else {
if (Tools.mkdir(settings.getBackupDir())) { if (Tools.mkdir(settings.getBackupDir())) {
if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_PLAIN) if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_PLAIN)
doBackupPlain(Tools.buildUri(settings.getBackupDir(), FileHelper.backupFilename(this, Constants.BackupType.PLAIN_TEXT))); doBackupPlain(Tools.buildUri(settings.getBackupDir(), BackupHelper.backupFilename(this, Constants.BackupType.PLAIN_TEXT)));
else if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT) else if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT)
doBackupCrypt(Tools.buildUri(settings.getBackupDir(), FileHelper.backupFilename(this, Constants.BackupType.ENCRYPTED))); doBackupCrypt(Tools.buildUri(settings.getBackupDir(), BackupHelper.backupFilename(this, Constants.BackupType.ENCRYPTED)));
else if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_PGP) else if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_PGP)
backupEncryptedWithPGP(Tools.buildUri(settings.getBackupDir(), FileHelper.backupFilename(this, Constants.BackupType.OPEN_PGP)), null); backupEncryptedWithPGP(Tools.buildUri(settings.getBackupDir(), BackupHelper.backupFilename(this, Constants.BackupType.OPEN_PGP)), null);
} else { } else {
Toast.makeText(this, R.string.backup_toast_mkdir_failed, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_mkdir_failed, Toast.LENGTH_LONG).show();
} }
@ -370,7 +409,7 @@ public class BackupActivity extends BaseActivity {
private void doRestorePlain(Uri uri) { private void doRestorePlain(Uri uri) {
if (Tools.isExternalStorageReadable()) { if (Tools.isExternalStorageReadable()) {
String content = FileHelper.readFileToString(this, uri); String content = StorageAccessHelper.loadFileString(this, uri);
restoreEntries(content); restoreEntries(content);
} else { } else {
@ -382,7 +421,7 @@ public class BackupActivity extends BaseActivity {
if (Tools.isExternalStorageWritable()) { if (Tools.isExternalStorageWritable()) {
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey); ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey);
if (FileHelper.writeStringToFile(this, uri, DatabaseHelper.entriesToString(entries))) if (StorageAccessHelper.saveFile(this, uri, DatabaseHelper.entriesToString(entries)))
Toast.makeText(this, R.string.backup_toast_export_success, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_export_success, Toast.LENGTH_LONG).show();
else else
Toast.makeText(this, R.string.backup_toast_export_failed, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_export_failed, Toast.LENGTH_LONG).show();
@ -415,34 +454,47 @@ public class BackupActivity extends BaseActivity {
/* Encrypted backup functions */ /* Encrypted backup functions */
private void doRestoreCrypt(final Uri uri) { private void doRestoreCrypt(final Uri uri, final boolean old_format) {
String password = settings.getBackupPasswordEnc(); String password = settings.getBackupPasswordEnc();
if (password.isEmpty()) { if (password.isEmpty()) {
PasswordEntryDialog pwDialog = new PasswordEntryDialog(this, PasswordEntryDialog.Mode.ENTER, new PasswordEntryDialog.PasswordEnteredCallback() { PasswordEntryDialog pwDialog = new PasswordEntryDialog(this, PasswordEntryDialog.Mode.ENTER, new PasswordEntryDialog.PasswordEnteredCallback() {
@Override @Override
public void onPasswordEntered(String newPassword) { public void onPasswordEntered(String newPassword) {
doRestoreCryptWithPassword(uri, newPassword); doRestoreCryptWithPassword(uri, newPassword, old_format);
} }
}); });
pwDialog.show(); pwDialog.show();
} else { } else {
doRestoreCryptWithPassword(uri, password); doRestoreCryptWithPassword(uri, password, old_format);
} }
} }
private void doRestoreCryptWithPassword(Uri uri, String password) { private void doRestoreCryptWithPassword(Uri uri, String password, boolean old_format) {
if (Tools.isExternalStorageReadable()) { if (Tools.isExternalStorageReadable()) {
boolean success = true; boolean success = true;
String decryptedString = ""; String decryptedString = "";
try { try {
byte[] encrypted = FileHelper.readFileToBytes(this, uri); byte[] data = StorageAccessHelper.loadFile(this, uri);
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password); if (old_format) {
byte[] decrypted = EncryptionHelper.decrypt(key, encrypted); SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
byte[] decrypted = EncryptionHelper.decrypt(key, data);
decryptedString = new String(decrypted, StandardCharsets.UTF_8); decryptedString = new String(decrypted, StandardCharsets.UTF_8);
} else {
byte[] iterBytes = Arrays.copyOfRange(data, 0, Constants.INT_LENGTH);
byte[] salt = Arrays.copyOfRange(data, Constants.INT_LENGTH, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH);
byte[] encrypted = Arrays.copyOfRange(data, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH, data.length);
int iter = ByteBuffer.wrap(iterBytes).getInt();
SecretKey key = EncryptionHelper.generateSymmetricKeyPBKDF2(password, iter, salt);
byte[] decrypted = EncryptionHelper.decrypt(key, encrypted);
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
}
} catch (Exception e) { } catch (Exception e) {
success = false; success = false;
e.printStackTrace(); e.printStackTrace();
@ -482,10 +534,20 @@ public class BackupActivity extends BaseActivity {
boolean success = true; boolean success = true;
try { try {
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password); int iter = EncryptionHelper.generateRandomIterations();
byte[] salt = EncryptionHelper.generateRandom(Constants.ENCRYPTION_IV_LENGTH);
SecretKey key = EncryptionHelper.generateSymmetricKeyPBKDF2(password, iter, salt);
byte[] encrypted = EncryptionHelper.encrypt(key, plain.getBytes(StandardCharsets.UTF_8)); byte[] encrypted = EncryptionHelper.encrypt(key, plain.getBytes(StandardCharsets.UTF_8));
FileHelper.writeBytesToFile(this, uri, encrypted); byte[] iterBytes = ByteBuffer.allocate(Constants.INT_LENGTH).putInt(iter).array();
byte[] data = new byte[Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH + encrypted.length];
System.arraycopy(iterBytes, 0, data, 0, Constants.INT_LENGTH);
System.arraycopy(salt, 0, data, Constants.INT_LENGTH, Constants.ENCRYPTION_IV_LENGTH);
System.arraycopy(encrypted, 0, data, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH, encrypted.length);
StorageAccessHelper.saveFile(this, uri, data);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
success = false; success = false;
@ -509,8 +571,7 @@ public class BackupActivity extends BaseActivity {
if (decryptIntent == null) if (decryptIntent == null)
decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY); decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY);
String input = FileHelper.readFileToString(this, uri); String input = StorageAccessHelper.loadFileString(this, uri);
Log.d("OpenPGP", input);
InputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)); InputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream os = new ByteArrayOutputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream();
@ -521,7 +582,7 @@ public class BackupActivity extends BaseActivity {
private void doBackupEncrypted(Uri uri, String data) { private void doBackupEncrypted(Uri uri, String data) {
if (Tools.isExternalStorageWritable()) { if (Tools.isExternalStorageWritable()) {
boolean success = FileHelper.writeStringToFile(this, uri, data); boolean success = StorageAccessHelper.saveFile(this, uri, data);
if (success) if (success)
Toast.makeText(this, R.string.backup_toast_export_success, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_export_success, Toast.LENGTH_LONG).show();

View file

@ -98,6 +98,7 @@ public class MainActivity extends BaseActivity
private ListView tagsDrawerListView; private ListView tagsDrawerListView;
private TagsAdapter tagsDrawerAdapter; private TagsAdapter tagsDrawerAdapter;
private ActionBarDrawerToggle tagsToggle; private ActionBarDrawerToggle tagsToggle;
private String filterString;
// QR code scanning // QR code scanning
private void scanQRCode(){ private void scanQRCode(){
@ -284,6 +285,10 @@ public class MainActivity extends BaseActivity
ManualEntryDialog.show(MainActivity.this, settings, adapter); ManualEntryDialog.show(MainActivity.this, settings, adapter);
} }
} }
if (savedInstanceState != null){
setFilterString(savedInstanceState.getString("filterString", ""));
}
} }
@Override @Override
@ -320,6 +325,11 @@ public class MainActivity extends BaseActivity
} }
} }
if (filterString != null) {
// ensure the current filter string is applied after a resume
setFilterString(this.filterString);
}
startUpdater(); startUpdater();
} }
@ -329,6 +339,12 @@ public class MainActivity extends BaseActivity
stopUpdater(); stopUpdater();
} }
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("filterString", filterString);
}
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals(getString(R.string.settings_key_label_size)) || if (key.equals(getString(R.string.settings_key_label_size)) ||
key.equals(getString(R.string.settings_key_label_scroll)) || key.equals(getString(R.string.settings_key_label_scroll)) ||
@ -455,11 +471,7 @@ public class MainActivity extends BaseActivity
@Override @Override
public boolean onQueryTextChange(String newText) { public boolean onQueryTextChange(String newText) {
if (newText.isEmpty()) setFilterString(newText);
adapter.filterByTags(tagsDrawerAdapter.getActiveTags());
else
adapter.getFilter().filter(newText);
return false; return false;
} }
}); });
@ -491,6 +503,15 @@ public class MainActivity extends BaseActivity
return true; return true;
} }
private void setFilterString(String newText) {
if (newText.isEmpty())
adapter.filterByTags(tagsDrawerAdapter.getActiveTags());
else
adapter.getFilter().filter(newText);
this.filterString = newText;
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId(); int id = item.getItemId();
@ -592,10 +613,13 @@ public class MainActivity extends BaseActivity
settings.setAllTagsToggle(checkedTextView.isChecked()); settings.setAllTagsToggle(checkedTextView.isChecked());
for(int i = 0; i < tagsDrawerListView.getChildCount(); i++) { for(int i = 0; i < tagsDrawerListView.getChildCount(); i++) {
CheckedTextView childCheckBox = (CheckedTextView)tagsDrawerListView.getChildAt(i); CheckedTextView childCheckBox = (CheckedTextView) tagsDrawerListView.getChildAt(i);
childCheckBox.setChecked(checkedTextView.isChecked()); childCheckBox.setChecked(checkedTextView.isChecked());
tagsDrawerAdapter.setTagState(childCheckBox.getText().toString(), childCheckBox.isChecked()); }
settings.setTagToggle(childCheckBox.getText().toString(), childCheckBox.isChecked());
for (String tag: tagsDrawerAdapter.getTags()) {
tagsDrawerAdapter.setTagState(tag, checkedTextView.isChecked());
settings.setTagToggle(tag, checkedTextView.isChecked());
} }
if(checkedTextView.isChecked()) { if(checkedTextView.isChecked()) {

View file

@ -31,7 +31,7 @@ public class AboutFragment extends Fragment {
private static final String MIT_URI = GITHUB_URI + "/blob/master/LICENSE.txt"; private static final String MIT_URI = GITHUB_URI + "/blob/master/LICENSE.txt";
private static final String AUTHOR1_GITHUB = "https://github.com/flocke"; private static final String AUTHOR1_GITHUB = "https://github.com/flocke";
private static final String AUTHOR1_EXTRA = "https://paypal.me/flocke000"; private static final String AUTHOR1_EXTRA = "https://flocke.shadowice.org/donate.html";
private static final String AUTHOR2_GITHUB = "https://github.com/richyhbm"; private static final String AUTHOR2_GITHUB = "https://github.com/richyhbm";
private static final String AUTHOR2_EXTRA = "https://richyhbm.co.uk/donate"; private static final String AUTHOR2_EXTRA = "https://richyhbm.co.uk/donate";

View file

@ -29,15 +29,17 @@ import android.net.Uri;
import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.R; import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.BackupHelper;
import org.shadowice.flocke.andotp.Utilities.Constants; import org.shadowice.flocke.andotp.Utilities.Constants;
import org.shadowice.flocke.andotp.Utilities.DatabaseHelper; import org.shadowice.flocke.andotp.Utilities.DatabaseHelper;
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper; import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
import org.shadowice.flocke.andotp.Utilities.FileHelper;
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper; import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
import org.shadowice.flocke.andotp.Utilities.NotificationHelper; import org.shadowice.flocke.andotp.Utilities.NotificationHelper;
import org.shadowice.flocke.andotp.Utilities.Settings; import org.shadowice.flocke.andotp.Utilities.Settings;
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
import org.shadowice.flocke.andotp.Utilities.Tools; import org.shadowice.flocke.andotp.Utilities.Tools;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -54,7 +56,7 @@ public class EncryptedBackupBroadcastReceiver extends BackupBroadcastReceiver {
if (!canSaveBackup(context)) if (!canSaveBackup(context))
return; return;
Uri savePath = Tools.buildUri(settings.getBackupDir(), FileHelper.backupFilename(context, Constants.BackupType.ENCRYPTED)); Uri savePath = Tools.buildUri(settings.getBackupDir(), BackupHelper.backupFilename(context, Constants.BackupType.ENCRYPTED));
String password = settings.getBackupPasswordEnc(); String password = settings.getBackupPasswordEnc();
@ -77,9 +79,21 @@ public class EncryptedBackupBroadcastReceiver extends BackupBroadcastReceiver {
String plain = DatabaseHelper.entriesToString(entries); String plain = DatabaseHelper.entriesToString(entries);
try { try {
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password); int iter = EncryptionHelper.generateRandomIterations();
byte[] salt = EncryptionHelper.generateRandom(Constants.ENCRYPTION_IV_LENGTH);
SecretKey key = EncryptionHelper.generateSymmetricKeyPBKDF2(password, iter, salt);
byte[] encrypted = EncryptionHelper.encrypt(key, plain.getBytes(StandardCharsets.UTF_8)); byte[] encrypted = EncryptionHelper.encrypt(key, plain.getBytes(StandardCharsets.UTF_8));
FileHelper.writeBytesToFile(context, savePath, encrypted);
byte[] iterBytes = ByteBuffer.allocate(Constants.INT_LENGTH).putInt(iter).array();
byte[] data = new byte[Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH + encrypted.length];
System.arraycopy(iterBytes, 0, data, 0, Constants.INT_LENGTH);
System.arraycopy(salt, 0, data, Constants.INT_LENGTH, Constants.ENCRYPTION_IV_LENGTH);
System.arraycopy(encrypted, 0, data, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH, encrypted.length);
StorageAccessHelper.saveFile(context, savePath, data);
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, savePath.getPath()); NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, savePath.getPath());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View file

@ -29,12 +29,13 @@ import android.net.Uri;
import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.R; import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.BackupHelper;
import org.shadowice.flocke.andotp.Utilities.Constants; import org.shadowice.flocke.andotp.Utilities.Constants;
import org.shadowice.flocke.andotp.Utilities.DatabaseHelper; import org.shadowice.flocke.andotp.Utilities.DatabaseHelper;
import org.shadowice.flocke.andotp.Utilities.FileHelper;
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper; import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
import org.shadowice.flocke.andotp.Utilities.NotificationHelper; import org.shadowice.flocke.andotp.Utilities.NotificationHelper;
import org.shadowice.flocke.andotp.Utilities.Settings; import org.shadowice.flocke.andotp.Utilities.Settings;
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
import org.shadowice.flocke.andotp.Utilities.Tools; import org.shadowice.flocke.andotp.Utilities.Tools;
import java.util.ArrayList; import java.util.ArrayList;
@ -52,7 +53,7 @@ public class PlainTextBackupBroadcastReceiver extends BackupBroadcastReceiver {
if (!canSaveBackup(context)) if (!canSaveBackup(context))
return; return;
Uri savePath = Tools.buildUri(settings.getBackupDir(), FileHelper.backupFilename(context, Constants.BackupType.PLAIN_TEXT)); Uri savePath = Tools.buildUri(settings.getBackupDir(), BackupHelper.backupFilename(context, Constants.BackupType.PLAIN_TEXT));
SecretKey encryptionKey = null; SecretKey encryptionKey = null;
@ -66,7 +67,7 @@ public class PlainTextBackupBroadcastReceiver extends BackupBroadcastReceiver {
if (Tools.isExternalStorageWritable()) { if (Tools.isExternalStorageWritable()) {
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey); ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey);
if (FileHelper.writeStringToFile(context, savePath, DatabaseHelper.entriesToString(entries))) { if (StorageAccessHelper.saveFile(context, savePath, DatabaseHelper.entriesToString(entries))) {
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, savePath.getPath()); NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, savePath.getPath());
} else { } else {
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_export_failed); NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_export_failed);

View file

@ -0,0 +1,31 @@
package org.shadowice.flocke.andotp.Utilities;
import android.content.Context;
public class BackupHelper {
public static String backupFilename(Context context, Constants.BackupType type) {
Settings settings = new Settings(context);
switch (type) {
case PLAIN_TEXT:
if (settings.getIsAppendingDateTimeToBackups()) {
return String.format(Constants.BACKUP_FILENAME_PLAIN_FORMAT, Tools.getDateTimeString());
} else {
return Constants.BACKUP_FILENAME_PLAIN;
}
case ENCRYPTED:
if (settings.getIsAppendingDateTimeToBackups()) {
return String.format(Constants.BACKUP_FILENAME_CRYPT_FORMAT, Tools.getDateTimeString());
} else {
return Constants.BACKUP_FILENAME_CRYPT;
}
case OPEN_PGP:
if (settings.getIsAppendingDateTimeToBackups()) {
return String.format(Constants.BACKUP_FILENAME_PGP_FORMAT, Tools.getDateTimeString());
} else {
return Constants.BACKUP_FILENAME_PGP;
}
}
return Constants.BACKUP_FILENAME_PLAIN;
}
}

View file

@ -58,24 +58,26 @@ public class Constants {
public final static int INTENT_MAIN_BACKUP = 102; public final static int INTENT_MAIN_BACKUP = 102;
public final static int INTENT_MAIN_INTRO = 103; public final static int INTENT_MAIN_INTRO = 103;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_PLAIN = 200; public final static int INTENT_BACKUP_OPEN_DOCUMENT_PLAIN = 200;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_PLAIN = 201; public final static int INTENT_BACKUP_SAVE_DOCUMENT_PLAIN = 201;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_CRYPT = 202; public final static int INTENT_BACKUP_OPEN_DOCUMENT_CRYPT = 202;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_CRYPT = 203; public final static int INTENT_BACKUP_SAVE_DOCUMENT_CRYPT = 203;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_PGP = 204; public final static int INTENT_BACKUP_OPEN_DOCUMENT_PGP = 204;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_PGP = 205; public final static int INTENT_BACKUP_SAVE_DOCUMENT_PGP = 205;
public final static int INTENT_BACKUP_ENCRYPT_PGP = 206; public final static int INTENT_BACKUP_ENCRYPT_PGP = 206;
public final static int INTENT_BACKUP_DECRYPT_PGP = 207; public final static int INTENT_BACKUP_DECRYPT_PGP = 207;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD = 208;
public static final int INTENT_SETTINGS_AUTHENTICATE = 300; public static final int INTENT_SETTINGS_AUTHENTICATE = 300;
// Permission requests (Format: A1x with A = parent Activity, x = number of the request) // Permission requests (Format: A1x with A = parent Activity, x = number of the request)
public final static int PERMISSIONS_BACKUP_READ_IMPORT_PLAIN = 210; public final static int PERMISSIONS_BACKUP_READ_IMPORT_PLAIN = 210;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_PLAIN = 211; public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_PLAIN = 211;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_CRYPT = 212; public final static int PERMISSIONS_BACKUP_READ_IMPORT_CRYPT = 212;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_CRYPT = 213; public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_CRYPT = 213;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_PGP = 214; public final static int PERMISSIONS_BACKUP_READ_IMPORT_PGP = 214;
public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_PGP = 215; public final static int PERMISSIONS_BACKUP_WRITE_EXPORT_PGP = 215;
public final static int PERMISSIONS_BACKUP_READ_IMPORT_CRYPT_OLD = 216;
// Intent extras // Intent extras
public final static String EXTRA_AUTH_PASSWORD_KEY = "password_key"; public final static String EXTRA_AUTH_PASSWORD_KEY = "password_key";
@ -92,7 +94,9 @@ public class Constants {
final static String ALGORITHM_ASYMMETRIC = "RSA/ECB/PKCS1Padding"; final static String ALGORITHM_ASYMMETRIC = "RSA/ECB/PKCS1Padding";
final static int ENCRYPTION_KEY_LENGTH = 16; // 128-bit encryption key (KeyStore-mode) final static int ENCRYPTION_KEY_LENGTH = 16; // 128-bit encryption key (KeyStore-mode)
final static int ENCRYPTION_IV_LENGTH = 12; public final static int ENCRYPTION_IV_LENGTH = 12;
public final static int INT_LENGTH = 4;
final static int PBKDF2_MIN_ITERATIONS = 1000; final static int PBKDF2_MIN_ITERATIONS = 1000;
final static int PBKDF2_MAX_ITERATIONS = 5000; final static int PBKDF2_MAX_ITERATIONS = 5000;

View file

@ -89,6 +89,14 @@ public class EncryptionHelper {
return new SecretKeySpec(data, 0, data.length, "AES"); return new SecretKeySpec(data, 0, data.length, "AES");
} }
public static SecretKey generateSymmetricKeyPBKDF2(String password, int iter, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iter, Constants.PBKDF2_LENGTH);
return secretKeyFactory.generateSecret(keySpec);
}
public static SecretKey generateSymmetricKeyFromPassword(String password) public static SecretKey generateSymmetricKeyFromPassword(String password)
throws NoSuchAlgorithmException { throws NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA-256"); MessageDigest sha = MessageDigest.getInstance("SHA-256");

View file

@ -76,9 +76,11 @@ public class EntryThumbnail {
ComputerBase(R.drawable.thumb_computerbase), ComputerBase(R.drawable.thumb_computerbase),
ConnectWiseManage(R.drawable.thumb_connectwise_manage), ConnectWiseManage(R.drawable.thumb_connectwise_manage),
CozyCloud(R.drawable.thumb_cozycloud), CozyCloud(R.drawable.thumb_cozycloud),
Crowdin(R.drawable.thumb_crowdin),
Dashlane(R.drawable.thumb_dashlane), Dashlane(R.drawable.thumb_dashlane),
Debian(R.drawable.thumb_debian), Debian(R.drawable.thumb_debian),
Degiro(R.drawable.thumb_degiro), Degiro(R.drawable.thumb_degiro),
Diaspora(R.drawable.thumb_diaspora),
Digidentity(R.drawable.thumb_digidentity), Digidentity(R.drawable.thumb_digidentity),
DigitalOcean(R.drawable.thumb_digital_ocean), DigitalOcean(R.drawable.thumb_digital_ocean),
Discord(R.drawable.thumb_discord), Discord(R.drawable.thumb_discord),
@ -120,10 +122,13 @@ public class EntryThumbnail {
INWX(R.drawable.thumb_inwx), INWX(R.drawable.thumb_inwx),
Itchio(R.drawable.thumb_itchio), Itchio(R.drawable.thumb_itchio),
Jagex(R.drawable.thumb_jagex), Jagex(R.drawable.thumb_jagex),
Joomla(R.drawable.thumb_joomla),
Kickstarter(R.drawable.thumb_kickstarter), Kickstarter(R.drawable.thumb_kickstarter),
Kraken(R.drawable.thumb_kraken), Kraken(R.drawable.thumb_kraken),
Kucoin(R.drawable.thumb_kucoin), Kucoin(R.drawable.thumb_kucoin),
LastPass(R.drawable.thumb_lastpass), LastPass(R.drawable.thumb_lastpass),
Lichess(R.drawable.thumb_lichess),
LinkedIn(R.drawable.thumb_linkedin),
Linode(R.drawable.thumb_linode), Linode(R.drawable.thumb_linode),
Liqui(R.drawable.thumb_liqui), Liqui(R.drawable.thumb_liqui),
LocalBitcoins(R.drawable.thumb_localbitcoins), LocalBitcoins(R.drawable.thumb_localbitcoins),

View file

@ -22,59 +22,16 @@
package org.shadowice.flocke.andotp.Utilities; package org.shadowice.flocke.andotp.Utilities;
import android.content.Context;
import android.net.Uri;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter;
public class FileHelper { class FileHelper {
public static String readFileToString(Context context, Uri file) { static byte[] readFileToBytes(File file) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
try {
InputStream inputStream = context.getContentResolver().openInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
reader.close();
inputStream.close();
} catch (Exception error) {
error.printStackTrace();
}
return(stringBuilder.toString());
}
public static boolean writeStringToFile(Context context, Uri file, String content) {
boolean success = true;
try {
OutputStream outputStream = context.getContentResolver().openOutputStream(file);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
writer.write(content);
writer.close();
outputStream.close();
} catch (Exception error) {
success = false;
error.printStackTrace();
}
return success;
}
public static byte[] readFileToBytes(File file) throws IOException {
final InputStream in = new FileInputStream(file); final InputStream in = new FileInputStream(file);
try { try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@ -89,63 +46,10 @@ public class FileHelper {
} }
} }
public static void writeBytesToFile(File file, byte[] data) throws IOException { static void writeBytesToFile(File file, byte[] data) throws IOException {
final OutputStream out = new FileOutputStream(file); try (OutputStream out = new FileOutputStream(file)) {
try {
out.write(data); out.write(data);
} finally {
out.close();
} }
} }
public static byte[] readFileToBytes(Context context, Uri file) throws IOException {
final InputStream in = context.getContentResolver().openInputStream(file);
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int count;
while ((count = in.read(buffer)) != -1) {
bytes.write(buffer, 0, count);
}
return bytes.toByteArray();
} finally {
in.close();
}
}
public static void writeBytesToFile(Context context, Uri file, byte[] data) throws IOException {
final OutputStream out = context.getContentResolver().openOutputStream(file);
try {
out.write(data);
} finally {
out.close();
}
}
public static String backupFilename(Context context, Constants.BackupType type) {
Settings settings = new Settings(context);
switch (type) {
case PLAIN_TEXT:
if (settings.getIsAppendingDateTimeToBackups()) {
return String.format(Constants.BACKUP_FILENAME_PLAIN_FORMAT, Tools.getDateTimeString());
} else {
return Constants.BACKUP_FILENAME_PLAIN;
}
case ENCRYPTED:
if (settings.getIsAppendingDateTimeToBackups()) {
return String.format(Constants.BACKUP_FILENAME_CRYPT_FORMAT, Tools.getDateTimeString());
} else {
return Constants.BACKUP_FILENAME_CRYPT;
}
case OPEN_PGP:
if (settings.getIsAppendingDateTimeToBackups()) {
return String.format(Constants.BACKUP_FILENAME_PGP_FORMAT, Tools.getDateTimeString());
} else {
return Constants.BACKUP_FILENAME_PGP;
}
}
return Constants.BACKUP_FILENAME_PLAIN;
}
} }

View file

@ -493,6 +493,14 @@ public class Settings {
setBoolean(R.string.settings_key_last_used_dialog_shown, value); setBoolean(R.string.settings_key_last_used_dialog_shown, value);
} }
public boolean getNewBackupFormatDialogShown() {
return getBoolean(R.string.settings_key_new_backup_format_dialog_shown, false);
}
public void setNewBackupFormatDialogShown(boolean value) {
setBoolean(R.string.settings_key_new_backup_format_dialog_shown, value);
}
public boolean getAndroidBackupServiceEnabled() { public boolean getAndroidBackupServiceEnabled() {
return getBoolean(R.string.settings_key_enable_android_backup_service, true); return getBoolean(R.string.settings_key_enable_android_backup_service, true);
} }

View file

@ -0,0 +1,65 @@
package org.shadowice.flocke.andotp.Utilities;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import org.apache.commons.codec.Charsets;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class StorageAccessHelper {
public static boolean saveFile(Context context, Uri file, byte[] data) {
boolean success = true;
try {
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(file, "w");
FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(data);
fileOutputStream.close();
pfd.close();
} catch (IOException e) {
e.printStackTrace();
success = false;
}
return success;
}
public static boolean saveFile(Context context, Uri file, String data) {
return saveFile(context, file, data.getBytes(Charsets.UTF_8));
}
public static byte[] loadFile(Context context, Uri file) throws IOException {
try (InputStream inputStream = context.getContentResolver().openInputStream(file)) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int count;
while ((count = inputStream.read(buffer)) != -1) {
bytes.write(buffer, 0, count);
}
return bytes.toByteArray();
}
}
public static String loadFileString(Context context, Uri file) {
String result = "";
try {
byte[] content = loadFile(context, file);
result = new String(content, Charsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}

View file

@ -66,9 +66,7 @@ public class TokenCalculator {
} }
public static String TOTP_RFC6238(byte[] secret, int period, int digits, HashAlgorithm algorithm) { public static String TOTP_RFC6238(byte[] secret, int period, int digits, HashAlgorithm algorithm) {
int token = TOTP_RFC6238(secret, period, System.currentTimeMillis() / 1000, digits, algorithm); return Tools.formatTokenString(TOTP_RFC6238(secret, period, System.currentTimeMillis() / 1000, digits, algorithm), digits);
return String.format("%0" + digits + "d", token);
} }
public static String TOTP_Steam(byte[] secret, int period, int digits, HashAlgorithm algorithm) { public static String TOTP_Steam(byte[] secret, int period, int digits, HashAlgorithm algorithm) {
@ -88,7 +86,7 @@ public class TokenCalculator {
int fullToken = HOTP(secret, counter, algorithm); int fullToken = HOTP(secret, counter, algorithm);
int div = (int) Math.pow(10, digits); int div = (int) Math.pow(10, digits);
return String.format("%0" + digits + "d", fullToken % div); return Tools.formatTokenString(fullToken % div, digits);
} }
private static int TOTP(byte[] key, int period, long time, HashAlgorithm algorithm) { private static int TOTP(byte[] key, int period, long time, HashAlgorithm algorithm) {

View file

@ -34,6 +34,7 @@ import android.os.Environment;
import java.io.File; import java.io.File;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -97,6 +98,14 @@ public class Tools {
} }
} }
public static String formatTokenString(int token, int digits) {
NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
numberFormat.setMinimumIntegerDigits(digits);
numberFormat.setGroupingUsed(false);
return numberFormat.format(token);
}
public static String formatToken(String s, int chunkSize) { public static String formatToken(String s, int chunkSize) {
if (chunkSize ==0 || s == null) if (chunkSize ==0 || s == null)
return s; return s;

View file

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m82.345,86.605c-8.2,-0 -12.928,-4.254 -14.397,-12.988 2.389,0.323 4.858,0.589 7.328,0.684 1.024,7.175 4.423,10.658 8.015,12.285 -0.316,0.012 -0.632,0.019 -0.946,0.019zM67.681,64.467c0.053,-0.599 0.114,-1.24 0.144,-1.416 1.969,-10.2 18.984,-14.186 28.078,-14.186 -8.628,2.028 -17.705,5.947 -19.639,13.332 -0.182,0.704 -0.482,1.967 -0.728,3.083 -1.669,-0.169 -2.544,-0.22 -3.28,-0.263 -1.094,-0.064 -1.772,-0.103 -4.575,-0.549zM119.327,49.109c0.6,-0.035 0.664,-0.895 0.074,-1.012 -3.96,-0.781 -10.225,-1.442 -17.101,-1.442 -15.406,-0 -33.876,3.319 -36.331,16.037 -0.091,0.478 -0.275,2.893 -0.327,3.36 1.405,0.234 2.439,0.397 3.241,0.514 -0.087,0.623 -0.142,1.263 -0.154,1.925 -0.021,1.174 0.003,2.277 0.061,3.326 -0.994,-0.129 -2.003,-0.271 -3.036,-0.426 1.242,13.17 8.481,16.934 16.06,17.099 0.177,0.004 0.354,0.008 0.531,0.008 4.788,-0 9.659,-1.38 13.192,-2.683 0.573,-0.211 0.364,-1.067 -0.241,-0.984 -6.978,0.949 -17.839,-0.243 -15.416,-17.754 2.007,-14.49 29.668,-17.393 39.447,-17.968"
android:fillColor="#2e2d2d"/>
<path
android:pathData="m54.119,58.836c-0.333,1.273 -0.898,3.658 -1.285,5.48 -3.173,-0.351 -5.145,-0.544 -6.776,-0.704 -2.358,-0.231 -3.812,-0.373 -6.931,-0.808 0.103,-1.178 0.246,-2.757 0.309,-3.106 3.966,-20.533 38.488,-22.75 53.147,-22.75 1.126,-0 2.234,0.012 3.322,0.033 -17.868,1.681 -37.891,6.961 -41.785,21.854zM62.347,96.564c-13.174,-0 -20.877,-7.34 -22.922,-21.827 4.254,0.604 8.691,1.116 13.103,1.234 1.654,13.614 8.745,18.654 15.936,20.122 -2.018,0.296 -4.076,0.471 -6.117,0.471zM37.578,59.339c-0.131,0.728 -0.423,4.378 -0.493,5.086 2.134,0.31 3.606,0.506 4.864,0.657 -0.103,0.827 -0.174,1.671 -0.19,2.544 -0.036,1.964 0.012,3.798 0.123,5.534 -1.513,-0.197 -3.05,-0.417 -4.621,-0.653 1.922,20.453 13.348,25.951 25.086,25.951 1.923,-0 3.854,-0.152 5.753,-0.405 6.663,-0.694 13.111,-3.011 17.313,-4.812 0.562,-0.241 0.301,-1.087 -0.299,-0.969 -9.941,1.946 -30.536,2.605 -26.469,-26.785 3.173,-22.946 48.89,-26.337 61.607,-26.836 0.596,-0.023 0.678,-0.872 0.097,-1.008 -5.767,-1.349 -16.196,-2.585 -27.767,-2.585 -23.325,-0 -51.283,5.025 -55.003,24.283"
android:fillColor="#2e2d2d"/>
<path
android:pathData="m4.03,48.381c-0.004,0.017 -0.007,0.031 -0.011,0.049 0.004,-0.018 0.007,-0.031 0.011,-0.049"
android:fillColor="#2e2d2d"/>
<path
android:pathData="m3.981,48.607c-0.007,0.032 -0.013,0.063 -0.02,0.097 -0.005,0.025 -0.011,0.054 -0.016,0.08 0.027,-0.128 0.052,-0.252 0.075,-0.353 -0.012,0.054 -0.025,0.115 -0.038,0.177"
android:fillColor="#2e2d2d"/>
<path
android:pathData="m3.342,51.984c0.077,-0.424 0.155,-0.858 0.233,-1.28 -0.078,0.421 -0.157,0.855 -0.233,1.28"
android:fillColor="#2e2d2d"/>
<path
android:pathData="m3.635,50.378c0.076,-0.405 0.149,-0.793 0.216,-1.13 -0.066,0.337 -0.14,0.725 -0.216,1.13"
android:fillColor="#2e2d2d"/>
<path
android:pathData="m90.284,23.074c-35.123,-0.001 -56.907,9.135 -62.995,26.415 -0.689,1.945 -2.034,6.082 -2.853,8.85 -1.924,-0.412 -3.489,-0.736 -4.813,-1.003 -2.093,-0.424 -3.577,-0.71 -4.882,-0.954 -3.144,-0.589 -4.98,-0.756 -11.708,-2.649 0.016,-0 0.033,-0.173 0.05,-0.269 0.244,-1.383 0.615,-3.469 0.853,-4.631 0.008,-0.042 0.018,-0.09 0.026,-0.129 0.007,-0.034 0.014,-0.065 0.02,-0.097 0.022,-0.102 0.042,-0.196 0.061,-0.275 0.016,-0.07 0.031,-0.131 0.044,-0.182 0.004,-0.015 0.011,-0.043 0.014,-0.056 6.651,-23.524 37.197,-28.427 61.653,-28.427l0.001,-0c14.772,-0 29.433,1.791 41.134,4.059 -5.111,-0.367 -10.748,-0.651 -16.605,-0.651zM31.542,99.778c4.303,4.349 10.051,7.006 17.134,7.931 -3.199,0.427 -6.52,0.69 -9.844,0.69 -23.769,-0 -36.177,-12.981 -36.907,-38.596 6.419,1.61 13.631,3.231 20.734,4.08 0.166,11.396 3.151,20.101 8.882,25.894zM3.851,49.248c-0.066,0.337 -0.14,0.725 -0.216,1.13 0.076,-0.405 0.149,-0.793 0.216,-1.13zM3.575,50.704c-0.078,0.421 -0.157,0.855 -0.233,1.28 0.077,-0.425 0.155,-0.859 0.233,-1.28zM127.673,27.184c-10.52,-4.045 -36.32,-9.477 -61.923,-9.476 -28.598,0 -56.95,6.779 -63.47,29.84 -0.299,1.064 -1.216,6.538 -1.425,7.594 3.099,0.796 5.301,1.342 7.028,1.748 -0.211,1.064 -0.394,2.147 -0.516,3.271 -0.343,3.147 -0.529,6.089 -0.595,8.871 -2.218,-0.52 -4.471,-1.078 -6.772,-1.67 0,34.518 19.661,42.93 38.832,42.93 2.753,-0 5.495,-0.176 8.169,-0.471 8.591,-0.947 16.975,-3.36 22.216,-5.103 0.584,-0.194 0.389,-1.065 -0.222,-0.99 -13.817,1.708 -47.123,1.354 -36.709,-44.396 8.051,-35.347 79.495,-32.272 95.166,-31.164 0.584,0.042 0.768,-0.774 0.221,-0.984"
android:fillColor="#2e2d2d"/>
<path
android:pathData="m4.1,48.095c-0.004,0.015 -0.009,0.037 -0.014,0.054 -0.013,0.05 -0.028,0.113 -0.044,0.182 -0.004,0.016 -0.008,0.033 -0.012,0.05 0.027,-0.116 0.051,-0.218 0.07,-0.286"
android:fillColor="#2e2d2d"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="128"
android:viewportHeight="128"
android:width="160dp"
android:height="160dp">
<path
android:pathData="M80.621094 117.238281C77.167969 112.230469 71.785156 104.414062 68.652344 99.867188C65.453125 95.222656 62.851562 91.601562 62.714844 91.597656C62.574219 91.597656 57.542969 98.621094 50.816406 108.21875C44.40625 117.359375 39.105469 124.839844 39.039062 124.839844C38.859375 124.839844 15.925781 108.058594 15.867188 107.882812C15.839844 107.796875 21.023438 99.890625 27.386719 90.3125C33.753906 80.734375 38.960938 72.75 38.960938 72.5625C38.960938 72.265625 36.898438 71.515625 20.71875 65.910156C10.6875 62.4375 2.382812 59.554688 2.261719 59.503906C2.09375 59.4375 3.050781 56.117188 6.324219 45.375C8.683594 37.652344 10.664062 31.273438 10.726562 31.195312C10.792969 31.117188 19.511719 34.015625 30.101562 37.636719C40.695312 41.257812 49.449219 44.21875 49.558594 44.21875C49.664062 44.21875 49.789062 44.050781 49.828125 43.84375C49.871094 43.636719 49.960938 34.269531 50.03125 23.023438C50.101562 11.777344 50.21875 2.484375 50.289062 2.367188C50.390625 2.207031 53.367188 2.160156 64.289062 2.164062C71.917969 2.164062 78.238281 2.222656 78.335938 2.289062C78.460938 2.371094 78.667969 8.554688 79.019531 22.441406C79.59375 45.242188 79.605469 45.546875 79.957031 45.546875C80.09375 45.546875 88.484375 42.628906 98.609375 39.0625C108.734375 35.492188 117.066406 32.632812 117.125 32.703125C117.332031 32.941406 125.632812 61.027344 125.53125 61.132812C125.476562 61.191406 117.027344 64.199219 106.757812 67.816406C92.628906 72.789062 88.070312 74.457031 88.03125 74.667969C88.003906 74.824219 92.863281 82.367188 99.039062 91.761719C105.121094 101.007812 110.078125 108.644531 110.050781 108.726562C109.988281 108.921875 87.230469 126.335938 87.039062 126.335938C86.960938 126.335938 84.074219 122.242188 80.621094 117.238281ZM80.621094 117.238281"
android:fillColor="#000000" />
</vector>

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m22.869,64.427 l-2.389,-2.389c-7.68,-7.68 -9.899,-18.432 -7.168,-28.16C5.632,32.171 -0,25.344 -0,17.152 -0,7.765 7.68,0.085 17.067,0.085c8.533,0 15.531,6.144 16.896,14.336 9.216,-2.219 19.285,0.341 26.453,7.509l1.024,1.024 -12.629,12.629 -1.024,-1.024c-4.096,-4.096 -10.752,-4.096 -14.848,0 -4.096,4.096 -4.096,10.752 0,14.848L61.269,77.739 48.64,90.368 35.328,77.056Z"
android:fillColor="#7ac143"/>
<path
android:pathData="M37.035,50.432 L50.347,37.12 62.976,24.491 65.365,22.101c7.509,-7.509 18.432,-9.899 27.989,-7.168 1.195,-8.363 8.363,-14.677 16.896,-14.677 9.387,0 17.067,7.68 17.067,17.067 0,8.704 -6.485,15.872 -14.848,16.896 2.731,9.557 0.341,20.309 -7.168,27.819l-1.024,1.024L91.989,50.432l1.024,-1.024c4.096,-4.096 4.096,-10.752 0,-14.848 -4.096,-4.096 -10.752,-4.096 -14.848,0L75.776,36.949 63.147,49.579 49.835,62.891Z"
android:fillColor="#f9a541"/>
<path
android:pathData="M93.867,114.091C84.139,116.992 73.216,114.773 65.536,107.093l-1.024,-1.024 12.629,-12.629 1.024,1.024c4.096,4.096 10.752,4.096 14.848,0 4.096,-4.096 4.096,-10.752 0,-14.848L90.624,77.227 77.995,64.597 64.683,51.285 77.312,38.656 105.813,67.157c7.168,7.168 9.728,17.408 7.509,26.795C121.685,95.147 128,102.315 128,110.848c0,9.387 -7.68,17.067 -17.067,17.067 -8.533,-0.171 -15.531,-6.144 -17.067,-13.824z"
android:fillColor="#f44321"/>
<path
android:pathData="m88.917,78.592 l-13.312,13.312 -12.629,12.629 -2.389,2.389c-7.339,7.339 -17.579,9.728 -26.795,7.509 -1.707,7.68 -8.533,13.312 -16.725,13.312 -9.387,0 -17.067,-7.68 -17.067,-17.067 0,-8.021 5.632,-14.848 13.141,-16.555 -2.389,-9.557 0,-19.797 7.339,-27.136l1.024,-1.024 12.629,12.629 -1.024,1.024c-4.096,4.096 -4.096,10.752 0,14.848 4.096,4.096 10.752,4.096 14.848,0L50.347,92.075 62.976,79.445 76.288,66.133Z"
android:fillColor="#5091cd"/>
</vector>

View file

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="M63.944,14.372C104.029,18.189 126.934,44.912 125.025,125.081H37.221c0,-34.358 38.176,-24.814 30.54,-80.169"
android:strokeLineJoin="round"
android:strokeWidth="5.7263422"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M71.579,44.912C73.03,56.021 50.392,73.048 41.039,79.27 29.586,86.905 30.273,95.838 21.951,94.54c-3.978,-3.589 5.383,-11.605 0,-11.453 -3.818,0 0.725,4.696 -3.818,7.635 -3.818,0 -15.282,3.818 -15.27,-15.27 0,-7.635 22.905,-45.811 22.905,-45.811 0,0 7.215,-7.253 7.635,-13.361 -2.787,-3.795 -1.909,-7.635 -1.909,-11.453 3.818,-3.818 11.453,9.544 11.453,9.544h7.635c0,0 2.978,-7.605 9.544,-11.453 3.818,0 3.818,11.453 3.818,11.453"
android:strokeLineJoin="round"
android:strokeWidth="5.7263422"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m16.225,73.544a1.909,1.909 0,1 1,-3.818 0,1.909 1.909,0 1,1 3.818,0zM36.965,36.323a1.909,5.726 30,1 1,-3.306 -1.909,1.909 5.726,30 1,1 3.306,1.909z"
android:strokeLineJoin="round"
android:strokeWidth="5.7263422"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m9.804,0c-5.191,0 -9.4,4.108 -9.4,9.171v109.659c0,5.064 4.209,9.171 9.4,9.171h108.393c5.191,0 9.4,-4.106 9.4,-9.171L127.597,9.171c0,-5.063 -4.209,-9.171 -9.4,-9.171zM29.479,21.486c6.572,0 10.62,4.316 10.745,9.987 0,5.547 -4.17,9.987 -10.869,9.987h-0.128c-6.447,0 -10.613,-4.441 -10.613,-9.987 0,-5.671 4.294,-9.987 10.866,-9.987zM86.114,47.995c12.64,0 22.119,8.26 22.119,26.014v33.142h-19.21v-30.922c0,-7.77 -2.78,-13.069 -9.731,-13.069 -5.307,0 -8.467,3.572 -9.856,7.023 -0.507,1.235 -0.633,2.961 -0.633,4.689v32.279h-19.21c0,0 0.252,-52.378 0,-57.801h19.21v8.194c2.551,-3.939 7.107,-9.548 17.312,-9.548zM68.802,57.542c-0.04,0.061 -0.089,0.13 -0.128,0.19h0.128zM19.748,49.349h19.21v57.801h-19.21z"
android:fillColor="#006699"/>
</vector>

View file

@ -31,7 +31,7 @@
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:inputType="textPassword" android:inputType="textPassword"
android:autofillHints="password" /> android:autofillHints="password" />
<requestFocus/>
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<Button <Button
@ -42,4 +42,4 @@
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:text="@string/auth_button_unlock" /> android:text="@string/auth_button_unlock" />
</LinearLayout> </LinearLayout>

View file

@ -129,6 +129,28 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/button_restore_crypt_old"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:text="@string/backup_title_import_crypt_old" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/backup_desc_import_crypt_old"/>
</LinearLayout>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -275,8 +275,7 @@
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold" android:textStyle="bold"
android:textColor="?attr/colorPaypal" android:text="@string/about_label_donate" />
android:text="@string/about_label_donate_paypal" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -333,8 +332,7 @@
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold" android:textStyle="bold"
android:textColor="@color/bitcoin" android:text="@string/about_label_donate" />
android:text="@string/about_label_donate_bitcoin" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -69,6 +69,6 @@
<b>Any entries that are added will get lost.</b>\n\nTo still be able to use andOTP you can go <b>Any entries that are added will get lost.</b>\n\nTo still be able to use andOTP you can go
to the <b>Settings</b> and switch the <b>Database encryption</b> to <b>Password / PIN</b>.</string> to the <b>Settings</b> and switch the <b>Database encryption</b> to <b>Password / PIN</b>.</string>
<!-- Shortcuts --> <!-- Shortcuts -->
<string name="shortcut_name_scan_qr">Scan QR-Code</string> <string name="shortcut_name_scan_qr">Escaneja un codi QR</string>
<string name="shortcut_name_enter_details">Enter details</string> <string name="shortcut_name_enter_details">Enter details</string>
</resources> </resources>

View file

@ -4,7 +4,7 @@
<!-- Buttons --> <!-- Buttons -->
<string name="button_cancel">Annuler</string> <string name="button_cancel">Annuler</string>
<string name="button_enter_details">Ajouter les détails</string> <string name="button_enter_details">Ajouter les détails</string>
<string name="button_scan_qr">Scanner un QR-Code</string> <string name="button_scan_qr">Scanner un code QR</string>
<string name="button_save">Enregistrer</string> <string name="button_save">Enregistrer</string>
<string name="button_new_tag">Nouveau tag</string> <string name="button_new_tag">Nouveau tag</string>
<string name="button_settings">Paramètres</string> <string name="button_settings">Paramètres</string>
@ -47,7 +47,7 @@
<string name="toast_auth_failed_fatal">Échec d\'authentification, fermeture d\'andOTP !</string> <string name="toast_auth_failed_fatal">Échec d\'authentification, fermeture d\'andOTP !</string>
<string name="toast_copied_to_clipboard">Copié dans le presse-papier</string> <string name="toast_copied_to_clipboard">Copié dans le presse-papier</string>
<string name="toast_entry_exists">Cette entrée existe déjà</string> <string name="toast_entry_exists">Cette entrée existe déjà</string>
<string name="toast_invalid_qr_code">QR-Code invalide</string> <string name="toast_invalid_qr_code">Code QR invalide</string>
<string name="toast_encryption_key_empty">Clé de chiffrement non chargée</string> <string name="toast_encryption_key_empty">Clé de chiffrement non chargée</string>
<!-- Dialogs --> <!-- Dialogs -->
<string name="dialog_title_auth">Identifiez-vous</string> <string name="dialog_title_auth">Identifiez-vous</string>
@ -66,6 +66,6 @@
<string name="dialog_msg_keystore_error">Échec du chargement de la clé de chiffrement à partir de l\'armoire à clés. <string name="dialog_msg_keystore_error">Échec du chargement de la clé de chiffrement à partir de l\'armoire à clés.
<b>Toute entrée ajoutée sera perdue.</b>\n\nPour continuer à utiliser andOTP, vous pouvez aller dans les <b>paramètres</b> pour passer de <b>Chiffrement de la base de données</b> à <b>Mot de passe / Code PIN</b>.</string> <b>Toute entrée ajoutée sera perdue.</b>\n\nPour continuer à utiliser andOTP, vous pouvez aller dans les <b>paramètres</b> pour passer de <b>Chiffrement de la base de données</b> à <b>Mot de passe / Code PIN</b>.</string>
<!-- Shortcuts --> <!-- Shortcuts -->
<string name="shortcut_name_scan_qr">Scanner le QR-code</string> <string name="shortcut_name_scan_qr">Scanner le code QR</string>
<string name="shortcut_name_enter_details">Ajouter les détails</string> <string name="shortcut_name_enter_details">Ajouter les détails</string>
</resources> </resources>

View file

@ -4,7 +4,7 @@
<!-- Buttons --> <!-- Buttons -->
<string name="button_cancel">Annulla</string> <string name="button_cancel">Annulla</string>
<string name="button_enter_details">Aggiungi dettagli</string> <string name="button_enter_details">Aggiungi dettagli</string>
<string name="button_scan_qr">Scansiona il QR Code</string> <string name="button_scan_qr">Scansiona il codice QR</string>
<string name="button_save">Salva</string> <string name="button_save">Salva</string>
<string name="button_new_tag">Nuovo tag</string> <string name="button_new_tag">Nuovo tag</string>
<string name="button_settings">Impostazioni</string> <string name="button_settings">Impostazioni</string>
@ -47,7 +47,7 @@
<string name="toast_auth_failed_fatal">Autenticazione non riuscita, andOTP verrà chiuso!</string> <string name="toast_auth_failed_fatal">Autenticazione non riuscita, andOTP verrà chiuso!</string>
<string name="toast_copied_to_clipboard">Copiato negli appunti</string> <string name="toast_copied_to_clipboard">Copiato negli appunti</string>
<string name="toast_entry_exists">Questa voce esiste già</string> <string name="toast_entry_exists">Questa voce esiste già</string>
<string name="toast_invalid_qr_code">QR code non valido</string> <string name="toast_invalid_qr_code">Codice QR non valido</string>
<string name="toast_encryption_key_empty">Chiave di crittografia non caricata</string> <string name="toast_encryption_key_empty">Chiave di crittografia non caricata</string>
<!-- Dialogs --> <!-- Dialogs -->
<string name="dialog_title_auth">Autenticazione</string> <string name="dialog_title_auth">Autenticazione</string>
@ -69,6 +69,6 @@
<b>Qualsiasi voce aggiunta verrà persa</b>\n\nPer continuare ad utilizzare andOTP, vai nelle <b>Qualsiasi voce aggiunta verrà persa</b>\n\nPer continuare ad utilizzare andOTP, vai nelle
<b>Impostazioni</b> e imposta la <b>Crittografia del database</b> su <b>Password / PIN</b>.</string> <b>Impostazioni</b> e imposta la <b>Crittografia del database</b> su <b>Password / PIN</b>.</string>
<!-- Shortcuts --> <!-- Shortcuts -->
<string name="shortcut_name_scan_qr">Scansione QR-Code</string> <string name="shortcut_name_scan_qr">Scansione codice QR</string>
<string name="shortcut_name_enter_details">Inserisci dettagli</string> <string name="shortcut_name_enter_details">Inserisci dettagli</string>
</resources> </resources>

View file

@ -0,0 +1,17 @@
<resources>
<!-- Light application theme -->
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:navigationBarColor">?android:attr/colorBackground</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
<style name="AppTheme.Dark" parent="AppBaseTheme.Dark">
<item name="android:navigationBarColor">?android:attr/colorBackground</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
<style name="AppTheme.Black" parent="AppBaseTheme.Black">
<item name="android:navigationBarColor">@color/black</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
</resources>

View file

@ -4,7 +4,7 @@
<!-- Buttons --> <!-- Buttons -->
<string name="button_cancel">取消</string> <string name="button_cancel">取消</string>
<string name="button_enter_details">鍵入詳細資訊</string> <string name="button_enter_details">鍵入詳細資訊</string>
<string name="button_scan_qr">掃描 QR-Code</string> <string name="button_scan_qr">掃描 QR code</string>
<string name="button_save">儲存</string> <string name="button_save">儲存</string>
<string name="button_new_tag">新標籤</string> <string name="button_new_tag">新標籤</string>
<string name="button_settings">設定</string> <string name="button_settings">設定</string>

View file

@ -8,12 +8,11 @@
<color name="github_dark">#333333</color> <color name="github_dark">#333333</color>
<color name="github_light">#f5f5f5</color> <color name="github_light">#f5f5f5</color>
<color name="paypal_dark">#003087</color>
<color name="paypal_light">#009cde</color>
<color name="bitcoin">#f7931a</color>
<color name="warning_red">#b71c1c</color> <!-- material_red_900 --> <color name="warning_red">#b71c1c</color> <!-- material_red_900 -->
<color name="black">#000000</color>
<color name="dark_thumbnail_background">#ffe0e0e0</color> <!-- material_grey_300 --> <color name="dark_thumbnail_background">#ffe0e0e0</color> <!-- material_grey_300 -->
<color name="fab_small_label_background">#212121</color> <color name="fab_small_label_background">#212121</color>

View file

@ -36,6 +36,7 @@
<string name="settings_key_openpgp_key_encrypt" translatable="false">pref_openpgp_key_encrypt</string> <string name="settings_key_openpgp_key_encrypt" translatable="false">pref_openpgp_key_encrypt</string>
<string name="settings_key_openpgp_key_sign" translatable="false">pref_openpgp_key_sign</string> <string name="settings_key_openpgp_key_sign" translatable="false">pref_openpgp_key_sign</string>
<string name="settings_key_openpgp_verify" translatable="false">pref_openpgp_verify</string> <string name="settings_key_openpgp_verify" translatable="false">pref_openpgp_verify</string>
<string name="settings_key_new_backup_format_dialog_shown" translatable="false">pref_new_backup_dialog_shown</string>
<string name="settings_key_security_backup_warning" translatable="false">pref_security_backup_warning_shown</string> <string name="settings_key_security_backup_warning" translatable="false">pref_security_backup_warning_shown</string>
<string name="settings_key_sort_mode" translatable="false">pref_sort_mode</string> <string name="settings_key_sort_mode" translatable="false">pref_sort_mode</string>

View file

@ -23,9 +23,7 @@
<string name="about_label_original_app">Original App</string> <string name="about_label_original_app">Original App</string>
<string name="about_label_website">Website</string> <string name="about_label_website">Website</string>
<string name="about_label_github" translatable="false">Github</string> <string name="about_label_github" translatable="false">Github</string>
<string name="about_label_donate_paypal">Donate (PayPal)</string> <string name="about_label_donate">Donate</string>
<string name="about_label_donate_bitcoin">Donate (Bitcoin)</string>
<string name="about_name_author1" translatable="false">Jakob Nixdorf</string> <string name="about_name_author1" translatable="false">Jakob Nixdorf</string>
<string name="about_name_author_original" translatable="false">Bruno Bierbaumer</string> <string name="about_name_author_original" translatable="false">Bruno Bierbaumer</string>
<string name="about_name_author2" translatable="false">Richy HBM</string> <string name="about_name_author2" translatable="false">Richy HBM</string>

View file

@ -11,6 +11,7 @@
<string name="backup_title_export_openpgp">Backup (OpenPGP)</string> <string name="backup_title_export_openpgp">Backup (OpenPGP)</string>
<string name="backup_title_import_plain">Restore (plain-text)</string> <string name="backup_title_import_plain">Restore (plain-text)</string>
<string name="backup_title_import_crypt">Restore (encrypted)</string> <string name="backup_title_import_crypt">Restore (encrypted)</string>
<string name="backup_title_import_crypt_old">Restore (encrypted, old encryption)</string>
<string name="backup_title_import_openpgp">Restore (OpenPGP)</string> <string name="backup_title_import_openpgp">Restore (OpenPGP)</string>
<string name="backup_title_replace">Replace existing entries</string> <string name="backup_title_replace">Replace existing entries</string>
@ -20,6 +21,8 @@
<string name="backup_desc_export_openpgp">Backup all accounts in an OpenPGP-encrypted JSON file</string> <string name="backup_desc_export_openpgp">Backup all accounts in an OpenPGP-encrypted JSON file</string>
<string name="backup_desc_import_plain">Restore accounts from a plain-text JSON file</string> <string name="backup_desc_import_plain">Restore accounts from a plain-text JSON file</string>
<string name="backup_desc_import_crypt">Restore accounts from a password-protected JSON file</string> <string name="backup_desc_import_crypt">Restore accounts from a password-protected JSON file</string>
<string name="backup_desc_import_crypt_old">Restore accounts from a password-protected JSON file
created with an <b>andOTP version lower than 0.6.3</b></string>
<string name="backup_desc_import_openpgp">Restore accounts from an OpenPGP-encrypted JSON file</string> <string name="backup_desc_import_openpgp">Restore accounts from an OpenPGP-encrypted JSON file</string>
<string name="backup_desc_crypt_setup">Failed to load the backup password from the <b>Settings</b>, <string name="backup_desc_crypt_setup">Failed to load the backup password from the <b>Settings</b>,
@ -61,6 +64,12 @@
<string name="backup_receiver_custom_encryption_failed">Password/PIN based encryption not <string name="backup_receiver_custom_encryption_failed">Password/PIN based encryption not
supported with broadcast backup</string> supported with broadcast backup</string>
<string name="backup_new_format_dialog_title">New encryption method</string>
<string name="backup_new_format_dialog_msg">Since version 0.6.3 of andOTP, a <b>new and improved
encryption method</b> is used for password-protected backups. The old backups can still be
imported, but it is <b>highly recommended to create new backups with the improved
encryption</b>.\n\nThis message will not be shown again.</string>
<!-- Notification channels --> <!-- Notification channels -->
<string name="notification_channel_name_backup_failed">Automatic backup failed</string> <string name="notification_channel_name_backup_failed">Automatic backup failed</string>
<string name="notification_channel_name_backup_success">Automatic backup successful</string> <string name="notification_channel_name_backup_success">Automatic backup successful</string>

View file

@ -4,7 +4,7 @@
<!-- Buttons --> <!-- Buttons -->
<string name="button_cancel">Cancel</string> <string name="button_cancel">Cancel</string>
<string name="button_enter_details">Enter details</string> <string name="button_enter_details">Enter details</string>
<string name="button_scan_qr">Scan QR-Code</string> <string name="button_scan_qr">Scan QR code</string>
<string name="button_save">Save</string> <string name="button_save">Save</string>
<string name="button_new_tag">New tag</string> <string name="button_new_tag">New tag</string>
<string name="button_settings">Settings</string> <string name="button_settings">Settings</string>
@ -86,6 +86,6 @@
to the <b>Settings</b> and switch the <b>Database encryption</b> to <b>Password / PIN</b>.</string> to the <b>Settings</b> and switch the <b>Database encryption</b> to <b>Password / PIN</b>.</string>
<!-- Shortcuts --> <!-- Shortcuts -->
<string name="shortcut_name_scan_qr">Scan QR-Code</string> <string name="shortcut_name_scan_qr">Scan QR code</string>
<string name="shortcut_name_enter_details">Enter details</string> <string name="shortcut_name_enter_details">Enter details</string>
</resources> </resources>

View file

@ -5,7 +5,6 @@
<attr name="dialogTheme" format="reference" /> <attr name="dialogTheme" format="reference" />
<attr name="colorGithub" format="reference" /> <attr name="colorGithub" format="reference" />
<attr name="colorPaypal" format="reference" />
<attr name="thumbnailBackground" format="reference" /> <attr name="thumbnailBackground" format="reference" />
<!-- General styles --> <!-- General styles -->
@ -16,7 +15,7 @@
</style> </style>
<!-- Light application theme --> <!-- Light application theme -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
@ -24,7 +23,6 @@
<item name="windowBackground">?android:attr/colorBackground</item> <item name="windowBackground">?android:attr/colorBackground</item>
<item name="colorGithub">@color/github_dark</item> <item name="colorGithub">@color/github_dark</item>
<item name="colorPaypal">@color/paypal_dark</item>
<item name="thumbnailBackground">@android:color/transparent</item> <item name="thumbnailBackground">@android:color/transparent</item>
<item name="cardStyle">@style/CardViewStyle</item> <item name="cardStyle">@style/CardViewStyle</item>
@ -37,6 +35,8 @@
<item name="about_libraries_dividerLight_openSource">@color/about_libraries_dividerLight_openSource</item> <item name="about_libraries_dividerLight_openSource">@color/about_libraries_dividerLight_openSource</item>
</style> </style>
<style name="AppTheme" parent="AppBaseTheme"/>
<style name="AppTheme.NoActionBar"> <style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
@ -51,7 +51,7 @@
</style> </style>
<!-- Dark application theme. --> <!-- Dark application theme. -->
<style name="AppTheme.Dark" parent="Theme.AppCompat"> <style name="AppBaseTheme.Dark" parent="Theme.AppCompat">
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
@ -60,7 +60,6 @@
<item name="thumbnailBackground">@color/dark_thumbnail_background</item> <item name="thumbnailBackground">@color/dark_thumbnail_background</item>
<item name="colorGithub">@color/github_light</item> <item name="colorGithub">@color/github_light</item>
<item name="colorPaypal">@color/paypal_light</item>
<item name="cardStyle">@style/CardViewStyle</item> <item name="cardStyle">@style/CardViewStyle</item>
<item name="dialogTheme">@style/DialogTheme.Dark</item> <item name="dialogTheme">@style/DialogTheme.Dark</item>
@ -72,6 +71,8 @@
<item name="about_libraries_dividerLight_openSource">@color/about_libraries_dividerLight_openSource_dark</item> <item name="about_libraries_dividerLight_openSource">@color/about_libraries_dividerLight_openSource_dark</item>
</style> </style>
<style name="AppTheme.Dark" parent = "AppBaseTheme.Dark"/>
<style name="AppTheme.Dark.NoActionBar"> <style name="AppTheme.Dark.NoActionBar">
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
@ -82,29 +83,30 @@
</style> </style>
<!-- Black application theme. --> <!-- Black application theme. -->
<style name="AppTheme.Black" parent="Theme.AppCompat"> <style name="AppBaseTheme.Black" parent="Theme.AppCompat">
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="windowBackground">@android:color/black</item> <item name="windowBackground">@color/black</item>
<item name="thumbnailBackground">@color/dark_thumbnail_background</item> <item name="thumbnailBackground">@color/dark_thumbnail_background</item>
<item name="colorGithub">@color/github_light</item> <item name="colorGithub">@color/github_light</item>
<item name="colorPaypal">@color/paypal_light</item>
<item name="cardStyle">@style/CardViewStyle.Black</item> <item name="cardStyle">@style/CardViewStyle.Black</item>
<item name="dialogTheme">@style/DialogTheme.Dark</item> <item name="dialogTheme">@style/DialogTheme.Dark</item>
<item name="about_libraries_card">@android:color/black</item> <item name="about_libraries_card">@color/black</item>
<item name="about_libraries_title_openSource">@color/about_libraries_text_openSource_dark</item> <item name="about_libraries_title_openSource">@color/about_libraries_text_openSource_dark</item>
<item name="about_libraries_text_openSource">@color/about_libraries_text_openSource_dark</item> <item name="about_libraries_text_openSource">@color/about_libraries_text_openSource_dark</item>
<item name="about_libraries_dividerDark_openSource">@color/about_libraries_dividerDark_openSource_dark</item> <item name="about_libraries_dividerDark_openSource">@color/about_libraries_dividerDark_openSource_dark</item>
<item name="about_libraries_dividerLight_openSource">@color/about_libraries_dividerLight_openSource_dark</item> <item name="about_libraries_dividerLight_openSource">@color/about_libraries_dividerLight_openSource_dark</item>
</style> </style>
<style name="AppTheme.Black" parent="AppBaseTheme.Black"/>
<style name="CardViewStyle.Black"> <style name="CardViewStyle.Black">
<item name="cardBackgroundColor">@android:color/black</item> <item name="cardBackgroundColor">@color/black</item>
</style> </style>
<style name="AppTheme.Black.NoActionBar"> <style name="AppTheme.Black.NoActionBar">