Merge pull request #1716 from open-keychain/backup-v2

Backup Format v2
This commit is contained in:
Dominik Schürmann
2016-02-10 19:14:56 +01:00
10 changed files with 400 additions and 1213 deletions

View File

@@ -50,6 +50,7 @@ dependencies {
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0' compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final' compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
compile 'com.splitwise:tokenautocomplete:2.0.2@aar' compile 'com.splitwise:tokenautocomplete:2.0.2@aar'
compile 'com.github.pinball83:masked-edittext:1.0.3'
compile 'se.emilsjolander:stickylistheaders:2.7.0' compile 'se.emilsjolander:stickylistheaders:2.7.0'
compile 'org.sufficientlysecure:html-textview:1.3' compile 'org.sufficientlysecure:html-textview:1.3'
compile 'org.sufficientlysecure:donations:2.4' compile 'org.sufficientlysecure:donations:2.4'
@@ -87,47 +88,48 @@ dependencies {
// Comment out the libs referenced as git submodules! // Comment out the libs referenced as git submodules!
dependencyVerification { dependencyVerification {
verify = [ verify = [
'com.android.support:support-v4:5c7dceb6c824089fe80f502e5206264048ef8bffa4e8ddeab180b81723e79b7f', 'com.android.support:support-v4:5c7dceb6c824089fe80f502e5206264048ef8bffa4e8ddeab180b81723e79b7f',
'com.android.support:appcompat-v7:0a8762214382b7e8d4b989b4ac10b5c846b957d767ccb7bccbc6be5afa885a82', 'com.android.support:appcompat-v7:0a8762214382b7e8d4b989b4ac10b5c846b957d767ccb7bccbc6be5afa885a82',
'com.android.support:design:41a9cd75ca78f25df5f573db7cedf8bb66beae00c330943923ba9f3e2051736d', 'com.android.support:design:41a9cd75ca78f25df5f573db7cedf8bb66beae00c330943923ba9f3e2051736d',
'com.android.support:recyclerview-v7:7606373da0931a1e62588335465a0e390cd676c98117edab29220317495faefd', 'com.android.support:recyclerview-v7:7606373da0931a1e62588335465a0e390cd676c98117edab29220317495faefd',
'com.android.support:cardview-v7:5a5bc04a278662bfafdea5b11b2108a4b354dca6c68958b312f6f45cc5fe2e38', 'com.android.support:cardview-v7:5a5bc04a278662bfafdea5b11b2108a4b354dca6c68958b312f6f45cc5fe2e38',
'com.eftimoff:android-patternview:594dde382fb9a445ef0c92d614f6f127727ce699f124de8167929e10f298bf8b', 'com.eftimoff:android-patternview:594dde382fb9a445ef0c92d614f6f127727ce699f124de8167929e10f298bf8b',
'com.journeyapps:zxing-android-embedded:90840a4457e68962fdfb74f691c6a736be7596291001045241901f1f0e6db2ac', 'com.journeyapps:zxing-android-embedded:90840a4457e68962fdfb74f691c6a736be7596291001045241901f1f0e6db2ac',
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311', 'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240', 'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240',
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13', 'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
'org.ocpsoft.prettytime:prettytime:ef7098d973ae78b57d1a22dc37d3b8a771bf030301300e24055d676b6cdc5e75', 'org.ocpsoft.prettytime:prettytime:ef7098d973ae78b57d1a22dc37d3b8a771bf030301300e24055d676b6cdc5e75',
'com.splitwise:tokenautocomplete:2fc238424130b42155b5f2e39799a90bbbd13b148850afbe534ab08bb913c7f7', 'com.splitwise:tokenautocomplete:2fc238424130b42155b5f2e39799a90bbbd13b148850afbe534ab08bb913c7f7',
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', 'com.github.pinball83:masked-edittext:b1913d86482c7066ebb7831696773ac131865dc441cf8a3fc41d3b7d5691724e',
'org.sufficientlysecure:html-textview:39048e35894e582adada388e6c00631803283f8defed8e07ad58a5f284f272ee', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f', 'org.sufficientlysecure:donations:96f8197bab26dfe41900d824f10f8f1914519cd62eedb77bdac5b223eccdf0a6',
'org.sufficientlysecure:donations:96f8197bab26dfe41900d824f10f8f1914519cd62eedb77bdac5b223eccdf0a6', 'org.sufficientlysecure:html-textview:39048e35894e582adada388e6c00631803283f8defed8e07ad58a5f284f272ee',
'com.squareup.okhttp:okhttp-urlconnection:8dce03792fd7b5f089dc4fc0fdcecbbe50ae6cca21cb08a787a3b902a9914111', 'com.squareup.okhttp:okhttp:8df336e3e93b22ba8c05da5d94caf968950db845869c7ca16ed682e065135aa8',
'com.squareup.okhttp:okhttp:8df336e3e93b22ba8c05da5d94caf968950db845869c7ca16ed682e065135aa8', 'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f',
'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633', 'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660',
'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660', 'com.squareup.okhttp:okhttp-urlconnection:8dce03792fd7b5f089dc4fc0fdcecbbe50ae6cca21cb08a787a3b902a9914111',
'com.cocosw:bottomsheet:40d2187c9cdaf224acbf876abc138cd9d4a293ef89091982ecf2492c8fcd186b', 'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4',
'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4', 'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633',
'com.mikepenz:materialize:2457dbe0b874a422c0a21bc6716cf5af1d5a8d39387857ff7c20855ab5543bf8', 'com.mikepenz:materialdrawer:4e2644f454cc2ce48b956536d3339957c3f592adb2e0b6dad72d477da29f7677',
'com.mikepenz:materialdrawer:4e2644f454cc2ce48b956536d3339957c3f592adb2e0b6dad72d477da29f7677', 'com.cocosw:bottomsheet:40d2187c9cdaf224acbf876abc138cd9d4a293ef89091982ecf2492c8fcd186b',
'com.mikepenz:google-material-typeface:48b2712de87d542e9b050846e9f602238a367f38e2d5e8ea4557c5b12adfcbec', 'com.mikepenz:iconics-core:d2495547db9d881168b1b502b1934f6a000ed5086c6c6a7114f3bbcbbb7ec306',
'com.mikepenz:iconics-core:d2495547db9d881168b1b502b1934f6a000ed5086c6c6a7114f3bbcbbb7ec306', 'com.mikepenz:materialize:2457dbe0b874a422c0a21bc6716cf5af1d5a8d39387857ff7c20855ab5543bf8',
'com.mikepenz:community-material-typeface:990acfcfb892a733d36748fe29176bd61dd5ab34bc8ca1c591200e639d955b99', 'com.mikepenz:fontawesome-typeface:69cb09934a83bac607e78a29459868d537f766224b4a65a042d1f84c98c7b05d',
'com.mikepenz:fontawesome-typeface:69cb09934a83bac607e78a29459868d537f766224b4a65a042d1f84c98c7b05d', 'com.mikepenz:google-material-typeface:48b2712de87d542e9b050846e9f602238a367f38e2d5e8ea4557c5b12adfcbec',
'com.fidesmo:nordpol-core:3de58e850a00bba5b4d3a604d1399bcd89f695ea191ec0b03a57222e18062d15', 'com.fidesmo:nordpol-android:56f43fe2b1676817bcb4085926de14a08282ef6729c855c198d81aec62b20d65',
'com.fidesmo:nordpol-android:56f43fe2b1676817bcb4085926de14a08282ef6729c855c198d81aec62b20d65', 'com.mikepenz:community-material-typeface:990acfcfb892a733d36748fe29176bd61dd5ab34bc8ca1c591200e639d955b99',
// 'OpenKeychain.extern.bouncycastle:core:b22dfb37e09fb520683dd0ba089351787560a75b59b60822143f633ec984cab5', // 'OpenKeychain.extern.bouncycastle:core:b22dfb37e09fb520683dd0ba089351787560a75b59b60822143f633ec984cab5',
// 'OpenKeychain.extern.openpgp-api-lib:openpgp-api:8a53012b9df6d62174ebdc11e0ab56700501a915930db5c12e32d565f136fc06', // 'OpenKeychain.extern.openpgp-api-lib:openpgp-api:8a53012b9df6d62174ebdc11e0ab56700501a915930db5c12e32d565f136fc06',
// 'OpenKeychain.extern.openkeychain-api-lib:openkeychain-intents:9263330c00497b7bb70502160f50c8396228129376f48f4f5656d28360a2edac', // 'OpenKeychain.extern.openkeychain-api-lib:openkeychain-intents:9263330c00497b7bb70502160f50c8396228129376f48f4f5656d28360a2edac',
// 'OpenKeychain.extern.bouncycastle:prov:2d93a52e1b519995b18c0a92a1e59a2773d67d9b466a9cce6af5202a66502577', // 'OpenKeychain.extern.bouncycastle:prov:2d93a52e1b519995b18c0a92a1e59a2773d67d9b466a9cce6af5202a66502577',
// 'OpenKeychain.extern.bouncycastle:pg:1397025acf36be36d329c0345b136af776be82fe5d6dad70cc06db09d2f02201', // 'OpenKeychain.extern.bouncycastle:pg:1397025acf36be36d329c0345b136af776be82fe5d6dad70cc06db09d2f02201',
// 'OpenKeychain.extern.safeslinger-exchange:safeslinger-exchange:989fcc0eba663489a41aa166f6bb39f21271d980faddea5f06ab75339e792d10', // 'OpenKeychain.extern.safeslinger-exchange:safeslinger-exchange:989fcc0eba663489a41aa166f6bb39f21271d980faddea5f06ab75339e792d10',
'com.android.support:support-annotations:f347a35b9748a4103b39a6714a77e2100f488d623fd6268e259c177b200e9d82', 'com.android.support:support-annotations:f347a35b9748a4103b39a6714a77e2100f488d623fd6268e259c177b200e9d82',
// 'OpenKeychain.extern:minidns:109d5851ab351d7628ed62a0ed96b40598952424e56657c17debbeb4a704f0ce', // 'OpenKeychain.extern:minidns:109d5851ab351d7628ed62a0ed96b40598952424e56657c17debbeb4a704f0ce',
// 'OpenKeychain.extern.KeybaseLib:Lib:c5b1567ff781c311240e83f865c4ba76ae435eb00994529b8364371abf0d76de', // 'OpenKeychain.extern.KeybaseLib:Lib:c5b1567ff781c311240e83f865c4ba76ae435eb00994529b8364371abf0d76de',
'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266', 'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266',
'com.fidesmo:nordpol-core:3de58e850a00bba5b4d3a604d1399bcd89f695ea191ec0b03a57222e18062d15',
] ]
} }

View File

@@ -278,7 +278,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
ArmorHeaders armorHeaders = parseArmorHeaders(in, log, indent); ArmorHeaders armorHeaders = parseArmorHeaders(in, log, indent);
String charset = armorHeaders.charset; String charset = armorHeaders.charset;
boolean useBackupCode = false; boolean useBackupCode = false;
if (armorHeaders.backupVersion != null && armorHeaders.backupVersion == 1) { if (armorHeaders.backupVersion != null && armorHeaders.backupVersion == 2) {
useBackupCode = true; useBackupCode = true;
} }

View File

@@ -152,7 +152,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
} }
// add proprietary header to indicate that this is a key backup // add proprietary header to indicate that this is a key backup
if (input.isAddBackupHeader()) { if (input.isAddBackupHeader()) {
armorOut.setHeader("BackupVersion", "1"); armorOut.setHeader("BackupVersion", "2");
} }
out = armorOut; out = armorOut;
} else { } else {

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -40,7 +41,7 @@ import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -50,9 +51,12 @@ import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator; import android.view.animation.AccelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import com.github.pinball83.maskededittext.MaskedEditText;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.ExportResult;
@@ -79,12 +83,20 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
public static final int REQUEST_SAVE = 1; public static final int REQUEST_SAVE = 1;
public static final String ARG_BACK_STACK = "back_stack"; public static final String ARG_BACK_STACK = "back_stack";
// https://github.com/open-keychain/open-keychain/wiki/Backups
// excludes 0 and O
private static final char[] mBackupCodeAlphabet =
new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
// argument variables // argument variables
private boolean mExportSecret; private boolean mExportSecret;
private long[] mMasterKeyIds; private long[] mMasterKeyIds;
String mBackupCode; String mBackupCode;
private EditText[] mCodeEditText; private MaskedEditText mCodeEditText;
private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator; private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
private Integer mBackStackLevel; private Integer mBackStackLevel;
@@ -96,7 +108,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
BackupCodeFragment frag = new BackupCodeFragment(); BackupCodeFragment frag = new BackupCodeFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(ARG_BACKUP_CODE, generateRandomCode()); args.putString(ARG_BACKUP_CODE, generateRandomBackupCode());
args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds); args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds);
args.putBoolean(ARG_EXPORT_SECRET, exportSecret); args.putBoolean(ARG_EXPORT_SECRET, exportSecret);
frag.setArguments(args); frag.setArguments(args);
@@ -132,14 +144,11 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
if (Constants.DEBUG && item.getItemId() == R.id.debug_accept_any_log) { if (Constants.DEBUG && item.getItemId() == R.id.debug_accept_any_log) {
boolean newCheckedState = !item.isChecked(); boolean newCheckedState = !item.isChecked();
item.setChecked(newCheckedState); item.setChecked(newCheckedState);
if (newCheckedState && TextUtils.isEmpty(mCodeEditText[0].getText())) { mDebugModeAcceptAnyCode = newCheckedState;
mCodeEditText[0].setText("ABCDEF"); if (newCheckedState) {
mCodeEditText[1].setText("GHIJKL"); mCodeEditText.setText("ABCD-EFGH-IJKL-MNOP-QRST-UVWX");
mCodeEditText[2].setText("MNOPQR");
mCodeEditText[3].setText("STUVW");
Notify.create(getActivity(), "Actual backup code is all 'A's", Style.WARN).show(); Notify.create(getActivity(), "Actual backup code is all 'A's", Style.WARN).show();
} }
mDebugModeAcceptAnyCode = newCheckedState;
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
@@ -161,9 +170,11 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mTitleAnimator.setDisplayedChild(1, animate); mTitleAnimator.setDisplayedChild(1, animate);
mStatusAnimator.setDisplayedChild(1, animate); mStatusAnimator.setDisplayedChild(1, animate);
mCodeFieldsAnimator.setDisplayedChild(1, animate); mCodeFieldsAnimator.setDisplayedChild(1, animate);
for (EditText editText : mCodeEditText) { // use non-breaking spaces to enlarge the empty EditText appropriately
editText.setText(""); String empty = "\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0" +
} "-\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0" +
"-\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0";
mCodeEditText.setText(empty);
pushBackStackEntry(); pushBackStackEntry();
@@ -177,7 +188,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
hideKeyboard(); hideKeyboard();
if (animate) { if (animate) {
@ColorInt int black = mCodeEditText[0].getCurrentTextColor(); @ColorInt int black = mCodeEditText.getCurrentTextColor();
@ColorInt int red = getResources().getColor(R.color.android_red_dark); @ColorInt int red = getResources().getColor(R.color.android_red_dark);
animateFlashText(mCodeEditText, black, red, false); animateFlashText(mCodeEditText, black, red, false);
} }
@@ -192,18 +203,14 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
hideKeyboard(); hideKeyboard();
for (EditText editText : mCodeEditText) { mCodeEditText.setEnabled(false);
editText.setEnabled(false);
}
@ColorInt int green = getResources().getColor(R.color.android_green_dark); @ColorInt int green = getResources().getColor(R.color.android_green_dark);
if (animate) { if (animate) {
@ColorInt int black = mCodeEditText[0].getCurrentTextColor(); @ColorInt int black = mCodeEditText.getCurrentTextColor();
animateFlashText(mCodeEditText, black, green, true); animateFlashText(mCodeEditText, black, green, true);
} else { } else {
for (TextView textView : mCodeEditText) { mCodeEditText.setTextColor(green);
textView.setTextColor(green);
}
} }
popBackStackNoAction(); popBackStackNoAction();
@@ -226,35 +233,23 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS); mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS);
mExportSecret = args.getBoolean(ARG_EXPORT_SECRET); mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
mCodeEditText = new EditText[4]; // NOTE: order of these method calls matter, see setupAutomaticLinebreak()
mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1); mCodeEditText = (MaskedEditText) view.findViewById(R.id.backup_code_input);
mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2); mCodeEditText.setInputType(
mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3); InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4); setupAutomaticLinebreak(mCodeEditText);
mCodeEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
{
TextView[] codeDisplayText = new TextView[4];
codeDisplayText[0] = (TextView) view.findViewById(R.id.backup_code_display_1);
codeDisplayText[1] = (TextView) view.findViewById(R.id.backup_code_display_2);
codeDisplayText[2] = (TextView) view.findViewById(R.id.backup_code_display_3);
codeDisplayText[3] = (TextView) view.findViewById(R.id.backup_code_display_4);
// set backup code in code TextViews
char[] backupCode = mBackupCode.toCharArray();
for (int i = 0; i < codeDisplayText.length; i++) {
codeDisplayText[i].setText(backupCode, i * 7, 6);
}
// set background to null in TextViews - this will retain padding from EditText style!
for (TextView textView : codeDisplayText) {
// noinspection deprecation, setBackground(Drawable) is API level >=16
textView.setBackgroundDrawable(null);
}
}
setupEditTextFocusNext(mCodeEditText);
setupEditTextSuccessListener(mCodeEditText); setupEditTextSuccessListener(mCodeEditText);
TextView codeDisplayText = (TextView) view.findViewById(R.id.backup_code_display);
setupAutomaticLinebreak(codeDisplayText);
// set background to null in TextViews - this will retain padding from EditText style!
// noinspection deprecation, setBackground(Drawable) is API level >=16
codeDisplayText.setBackgroundDrawable(null);
codeDisplayText.setText(mBackupCode);
mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator); mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator);
mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator); mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator);
mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator); mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator);
@@ -331,76 +326,67 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel); outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel);
} }
private void setupEditTextSuccessListener(final EditText[] backupCodes) { /**
for (EditText backupCode : backupCodes) { * Automatic line break with max 6 lines for smaller displays
* <p/>
backupCode.addTextChangedListener(new TextWatcher() { * NOTE: I was not able to get this behaviour using XML!
@Override * Looks like the order of these method calls matter, see http://stackoverflow.com/a/11171307
public void beforeTextChanged(CharSequence s, int start, int count, int after) { */
private void setupAutomaticLinebreak(TextView textview) {
} textview.setSingleLine(true);
textview.setMaxLines(6);
@Override textview.setHorizontallyScrolling(false);
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.length() > 6) {
throw new AssertionError("max length of each field is 6!");
}
boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
|| mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
boolean partIsComplete = s.length() == 6;
if (!inInputState || !partIsComplete) {
return;
}
checkIfCodeIsCorrect();
}
});
}
} }
private void checkIfCodeIsCorrect() { private void setupEditTextSuccessListener(final MaskedEditText backupCode) {
backupCode.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
|| mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
boolean partIsComplete = (backupCode.getText().toString().indexOf(' ') == -1)
&& (backupCode.getText().toString().indexOf('\u00a0') == -1);
if (!inInputState || !partIsComplete) {
return;
}
checkIfCodeIsCorrect(backupCode);
}
});
}
private void checkIfCodeIsCorrect(EditText backupCode) {
if (Constants.DEBUG && mDebugModeAcceptAnyCode) { if (Constants.DEBUG && mDebugModeAcceptAnyCode) {
switchState(BackupCodeState.STATE_OK, true); switchState(BackupCodeState.STATE_OK, true);
return; return;
} }
StringBuilder backupCodeInput = new StringBuilder(26); if (backupCode.toString().equals(mBackupCode)) {
for (EditText editText : mCodeEditText) {
if (editText.getText().length() < 6) {
return;
}
backupCodeInput.append(editText.getText());
backupCodeInput.append('-');
}
backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
// if they don't match, do nothing
if (backupCodeInput.toString().equals(mBackupCode)) {
switchState(BackupCodeState.STATE_OK, true); switchState(BackupCodeState.STATE_OK, true);
return; return;
} }
switchState(BackupCodeState.STATE_INPUT_ERROR, true); switchState(BackupCodeState.STATE_INPUT_ERROR, true);
} }
private static void animateFlashText( private static void animateFlashText(
final TextView[] textViews, int color1, int color2, boolean staySecondColor) { final TextView textView, int color1, int color2, boolean staySecondColor) {
ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2); ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2);
anim.addUpdateListener(new AnimatorUpdateListener() { anim.addUpdateListener(new AnimatorUpdateListener() {
@Override @Override
public void onAnimationUpdate(ValueAnimator animator) { public void onAnimationUpdate(ValueAnimator animator) {
for (TextView textView : textViews) { textView.setTextColor((Integer) animator.getAnimatedValue());
textView.setTextColor((Integer) animator.getAnimatedValue());
}
} }
}); });
anim.setRepeatMode(ValueAnimator.REVERSE); anim.setRepeatMode(ValueAnimator.REVERSE);
@@ -411,34 +397,6 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
} }
private static void setupEditTextFocusNext(final EditText[] backupCodes) {
for (int i = 0; i < backupCodes.length - 1; i++) {
final int next = i + 1;
backupCodes[i].addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
boolean inserting = before < count;
boolean cursorAtEnd = (start + count) == 6;
if (inserting && cursorAtEnd) {
backupCodes[next].requestFocus();
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
}
private void pushBackStackEntry() { private void pushBackStackEntry() {
if (mBackStackLevel != null) { if (mBackStackLevel != null) {
return; return;
@@ -557,7 +515,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
public BackupKeyringParcel createOperationInput() { public BackupKeyringParcel createOperationInput() {
Passphrase passphrase = new Passphrase(mBackupCode); Passphrase passphrase = new Passphrase(mBackupCode);
if (Constants.DEBUG && mDebugModeAcceptAnyCode) { if (Constants.DEBUG && mDebugModeAcceptAnyCode) {
passphrase = new Passphrase("AAAAAA-AAAAAA-AAAAAA-AAAAAA"); passphrase = new Passphrase("AAAA-AAAA-AAAA-AAAA-AAAA-AAAA");
} }
return new BackupKeyringParcel(passphrase, mMasterKeyIds, mExportSecret, mCachedBackupUri); return new BackupKeyringParcel(passphrase, mMasterKeyIds, mExportSecret, mCachedBackupUri);
} }
@@ -578,22 +536,26 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mCachedBackupUri = null; mCachedBackupUri = null;
} }
/**
* Generate backup code using format defined in
* https://github.com/open-keychain/open-keychain/wiki/Backups
*/
@NonNull @NonNull
private static String generateRandomCode() { private static String generateRandomBackupCode() {
Random r = new SecureRandom(); Random r = new SecureRandom();
// simple generation of a 20 character backup code // simple generation of a 24 character backup code
StringBuilder code = new StringBuilder(28); StringBuilder code = new StringBuilder(28);
for (int i = 0; i < 24; i++) { for (int i = 0; i < 24; i++) {
if (i == 6 || i == 12 || i == 18) { if (i == 4 || i == 8 || i == 12 || i == 16 || i == 20) {
code.append('-'); code.append('-');
} }
code.append((char) ('A' + r.nextInt(26)));
code.append(mBackupCodeAlphabet[r.nextInt(mBackupCodeAlphabet.length)]);
} }
return code.toString(); return code.toString();
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2014-2016 Dominik Schürmann <dominik@dominikschuermann.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -46,6 +46,8 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.ViewAnimator; import android.widget.ViewAnimator;
import com.github.pinball83.maskededittext.MaskedEditText;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
@@ -158,7 +160,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener { public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
private EditText mPassphraseEditText; private EditText mPassphraseEditText;
private TextView mPassphraseText; private TextView mPassphraseText;
private EditText[] mBackupCodeEditText; private MaskedEditText mBackupCodeEditText;
private boolean mIsCancelled = false; private boolean mIsCancelled = false;
private RequiredInputParcel mRequiredInput; private RequiredInputParcel mRequiredInput;
@@ -185,12 +187,13 @@ public class PassphraseDialogActivity extends FragmentActivity {
View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null); View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null);
alert.setView(view); alert.setView(view);
mBackupCodeEditText = new EditText[4]; mBackupCodeEditText = (MaskedEditText) view.findViewById(R.id.backup_code);
mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1); // NOTE: order of these method calls matter, see setupAutomaticLinebreak()
mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2); mBackupCodeEditText.setInputType(
mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3); InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4); setupAutomaticLinebreak(mBackupCodeEditText);
setupEditTextFocusNext(mBackupCodeEditText); mBackupCodeEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
mBackupCodeEditText.setOnEditorActionListener(this);
AlertDialog dialog = alert.create(); AlertDialog dialog = alert.create();
dialog.setButton(DialogInterface.BUTTON_POSITIVE, dialog.setButton(DialogInterface.BUTTON_POSITIVE,
@@ -324,32 +327,16 @@ public class PassphraseDialogActivity extends FragmentActivity {
return dialog; return dialog;
} }
private static void setupEditTextFocusNext(final EditText[] backupCodes) { /**
for (int i = 0; i < backupCodes.length - 1; i++) { * Automatic line break with max 6 lines for smaller displays
* <p/>
final int next = i + 1; * NOTE: I was not able to get this behaviour using XML!
* Looks like the order of these method calls matter, see http://stackoverflow.com/a/11171307
backupCodes[i].addTextChangedListener(new TextWatcher() { */
@Override private void setupAutomaticLinebreak(TextView textview) {
public void beforeTextChanged(CharSequence s, int start, int count, int after) { textview.setSingleLine(true);
} textview.setMaxLines(6);
textview.setHorizontallyScrolling(false);
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
boolean inserting = before < count;
boolean cursorAtEnd = (start + count) == 6;
if (inserting && cursorAtEnd) {
backupCodes[next].requestFocus();
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
} }
@Override @Override
@@ -363,17 +350,8 @@ public class PassphraseDialogActivity extends FragmentActivity {
public void onClick(View v) { public void onClick(View v) {
if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) { if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) {
StringBuilder backupCodeInput = new StringBuilder(26); Passphrase passphrase =
for (EditText editText : mBackupCodeEditText) { new Passphrase(mBackupCodeEditText.getText().toString());
if (editText.getText().length() < 6) {
return;
}
backupCodeInput.append(editText.getText());
backupCodeInput.append('-');
}
backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
Passphrase passphrase = new Passphrase(backupCodeInput.toString());
finishCaching(passphrase); finishCaching(passphrase);
return; return;

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto" xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -56,200 +57,39 @@
android:outAnimation="@anim/fade_out" android:outAnimation="@anim/fade_out"
custom:initialView="1"> custom:initialView="1">
<LinearLayout <TextView
android:id="@+id/backup_code_display"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"> android:layout_gravity="center_horizontal"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:clickable="false"
android:focusable="false"
android:singleLine="true"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="AAAA-AAAA-AAAA-AAAA-AAAA-AAAA" />
<TextView <com.github.pinball83.maskededittext.MaskedEditText
android:id="@+id/backup_code_display_1" android:id="@+id/backup_code_input"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="ABCDEF" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_2"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="GHIJKL" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_3"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="MNOPQR" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_4"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:singleLine="true"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="STUVWX" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"> android:layout_gravity="center_horizontal"
android:layout_marginLeft="16dp"
<!-- android:layout_marginRight="16dp"
The most reliable way to correctly size these I found was to put a transparent hint on them. android:textSize="18dp"
Theoretically, this should be what the android:ems attribute is for - didn't work for me. android:textStyle="bold"
--> android:typeface="monospace"
<EditText app:deleteChar="\u00a0"
android:id="@+id/backup_code_1" app:mask="****-****-****-****-****-****"
android:layout_width="wrap_content" app:maskIconColor="@color/colorPrimary"
android:layout_height="wrap_content" app:notMaskedSymbol="*"
android:layout_gravity="center_horizontal" app:replacementChar="\u00a0"
android:hint="ABCDEF" tools:ignore="SpUsage" />
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
</LinearLayout>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator> </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>

View File

@@ -1,359 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="50dp">
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/title_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
custom:initialView="0">
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_explanation" />
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_enter" />
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_ok" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/code_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="15dp"
android:layout_marginTop="15dp"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
custom:initialView="0">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<TextView
android:id="@+id/backup_code_display_1"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="ABCDEF" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_2"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="GHIJKL" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_3"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="MNOPQR" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_4"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:singleLine="true"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="STUVWX" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<!--
The most reliable way to correctly size these I found was to put a transparent hint on them.
Theoretically, this should be what the android:ems attribute is for - didn't work for me.
-->
<EditText
android:id="@+id/backup_code_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="16dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
</LinearLayout>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/status_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out"
custom:initialView="2">
<Button
android:id="@+id/button_backup_input"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:drawableLeft="@drawable/ic_mode_edit_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_code_wrotedown" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/backup_code_wrong" />
<Button
android:id="@+id/button_backup_back"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:drawableLeft="@drawable/ic_repeat_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_back" />
</LinearLayout>
<LinearLayout
style="?android:buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
style="?android:buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<Button
android:id="@+id/button_backup_share"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_share_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_share" />
<Button
android:id="@+id/button_backup_save"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_save_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_save" />
</LinearLayout>
<Button
android:id="@+id/button_faq"
style="?android:buttonBarButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/how_to_import" />
</LinearLayout>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
</LinearLayout>

View File

@@ -1,359 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="50dp">
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/title_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
custom:initialView="0">
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_explanation" />
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_enter" />
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_ok" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/code_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="15dp"
android:layout_marginTop="15dp"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
custom:initialView="0">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<TextView
android:id="@+id/backup_code_display_1"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="ABCDEF" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_2"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="GHIJKL" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_3"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="MNOPQR" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:id="@+id/backup_code_display_4"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="false"
android:focusable="false"
android:singleLine="true"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="STUVWX" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<!--
The most reliable way to correctly size these I found was to put a transparent hint on them.
Theoretically, this should be what the android:ems attribute is for - didn't work for me.
-->
<EditText
android:id="@+id/backup_code_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
</LinearLayout>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/status_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out"
custom:initialView="2">
<Button
android:id="@+id/button_backup_input"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:drawableLeft="@drawable/ic_mode_edit_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_code_wrotedown" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/backup_code_wrong" />
<Button
android:id="@+id/button_backup_back"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:drawableLeft="@drawable/ic_repeat_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_back" />
</LinearLayout>
<LinearLayout
style="?android:buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
style="?android:buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<Button
android:id="@+id/button_backup_share"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_share_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_share" />
<Button
android:id="@+id/button_backup_save"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_save_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_save" />
</LinearLayout>
<Button
android:id="@+id/button_faq"
style="?android:buttonBarButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/how_to_import" />
</LinearLayout>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
</LinearLayout>

View File

@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="20dp">
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/title_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
custom:initialView="0">
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_explanation" />
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_enter" />
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_ok" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/code_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="15dp"
android:layout_marginTop="15dp"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
custom:initialView="1">
<TextView
android:id="@+id/backup_code_display"
style="@android:style/Widget.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:clickable="false"
android:focusable="false"
android:singleLine="true"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="SpUsage"
tools:text="AAAA-AAAA-AAAA-AAAA-AAAA-AAAA" />
<com.github.pinball83.maskededittext.MaskedEditText
android:id="@+id/backup_code_input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textSize="18dp"
android:textStyle="bold"
android:typeface="monospace"
app:deleteChar="\u00a0"
app:mask="****-****-****-****-****-****"
app:maskIconColor="@color/colorPrimary"
app:notMaskedSymbol="*"
app:replacementChar="\u00a0"
tools:ignore="SpUsage" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/status_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out"
custom:initialView="2">
<Button
android:id="@+id/button_backup_input"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:drawableLeft="@drawable/ic_mode_edit_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_code_wrotedown" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/backup_code_wrong" />
<Button
android:id="@+id/button_backup_back"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:drawableLeft="@drawable/ic_repeat_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_back" />
</LinearLayout>
<LinearLayout
style="?android:buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
style="?android:buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<Button
android:id="@+id/button_backup_share"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_share_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_share" />
<Button
android:id="@+id/button_backup_save"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_save_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_save" />
</LinearLayout>
<Button
android:id="@+id/button_faq"
style="?android:buttonBarButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/how_to_import" />
</LinearLayout>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
</LinearLayout>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -27,99 +28,22 @@
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"> android:layout_marginTop="8dp">
<!-- <com.github.pinball83.maskededittext.MaskedEditText
The most reliable way to correctly size these I found was to put a transparent hint on them. android:id="@+id/backup_code"
Theoretically, this should be what the android:ems attribute is for - didn't work for me.
-->
<EditText
android:id="@+id/backup_code_1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:hint="ABCDEF" android:layout_marginLeft="16dp"
android:inputType="textNoSuggestions|textCapCharacters" android:layout_marginRight="16dp"
android:maxLength="6" android:textSize="18dp"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="14dp"
android:textStyle="bold" android:textStyle="bold"
android:typeface="monospace" android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" /> app:deleteChar="\u00a0"
app:mask="****-****-****-****-****-****"
<TextView app:maskIconColor="@color/colorPrimary"
android:layout_width="wrap_content" app:notMaskedSymbol="*"
android:layout_height="match_parent" app:replacementChar="\u00a0"
android:gravity="center_vertical" tools:ignore="SpUsage" />
android:text="-"
android:textSize="14dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="14dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="14dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="14dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="-"
android:textSize="14dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
<EditText
android:id="@+id/backup_code_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ABCDEF"
android:inputType="textNoSuggestions|textCapCharacters"
android:maxLength="6"
android:singleLine="true"
android:textColorHint="@android:color/transparent"
android:textSize="14dp"
android:textStyle="bold"
android:typeface="monospace"
tools:ignore="HardcodedText,SpUsage" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>