Revert usage of masked-edittext, back to plain edittexts for backup codes

This commit is contained in:
Dominik Schürmann
2016-04-12 19:06:08 +02:00
parent b55c505054
commit f7654af00b
8 changed files with 1607 additions and 191 deletions

View File

@@ -18,14 +18,6 @@
package org.sufficientlysecure.keychain.ui;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -41,9 +33,8 @@ import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -52,12 +43,9 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import com.github.pinball83.maskededittext.MaskedEditText;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
@@ -71,6 +59,14 @@ import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringParcel, ExportResult>
implements OnBackStackChangedListener {
@@ -96,7 +92,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
private long[] mMasterKeyIds;
String mBackupCode;
private MaskedEditText mCodeEditText;
private EditText[] mCodeEditText;
private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
private Integer mBackStackLevel;
@@ -146,8 +142,13 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
boolean newCheckedState = !item.isChecked();
item.setChecked(newCheckedState);
mDebugModeAcceptAnyCode = newCheckedState;
if (newCheckedState) {
mCodeEditText.setText("ABCD-EFGH-IJKL-MNOP-QRST-UVWX");
if (newCheckedState && TextUtils.isEmpty(mCodeEditText[0].getText())) {
mCodeEditText[0].setText("ABCD");
mCodeEditText[1].setText("EFGH");
mCodeEditText[2].setText("IJKL");
mCodeEditText[3].setText("MNOP");
mCodeEditText[4].setText("QRST");
mCodeEditText[5].setText("UVWX");
Notify.create(getActivity(), "Actual backup code is all 'A's", Style.WARN).show();
}
return true;
@@ -171,7 +172,9 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mTitleAnimator.setDisplayedChild(1, animate);
mStatusAnimator.setDisplayedChild(1, animate);
mCodeFieldsAnimator.setDisplayedChild(1, animate);
mCodeEditText.setText(" - - - - - ");
for (EditText editText : mCodeEditText) {
editText.setText("");
}
pushBackStackEntry();
@@ -185,7 +188,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
hideKeyboard();
if (animate) {
@ColorInt int black = mCodeEditText.getCurrentTextColor();
@ColorInt int black = mCodeEditText[0].getCurrentTextColor();
@ColorInt int red = getResources().getColor(R.color.android_red_dark);
animateFlashText(mCodeEditText, black, red, false);
}
@@ -200,14 +203,18 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
hideKeyboard();
mCodeEditText.setEnabled(false);
for (EditText editText : mCodeEditText) {
editText.setEnabled(false);
}
@ColorInt int green = getResources().getColor(R.color.android_green_dark);
if (animate) {
@ColorInt int black = mCodeEditText.getCurrentTextColor();
@ColorInt int black = mCodeEditText[0].getCurrentTextColor();
animateFlashText(mCodeEditText, black, green, true);
} else {
mCodeEditText.setTextColor(green);
for (TextView textView : mCodeEditText) {
textView.setTextColor(green);
}
}
popBackStackNoAction();
@@ -230,47 +237,39 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS);
mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
// NOTE: order of these method calls matter, see setupAutomaticLinebreak()
mCodeEditText = (MaskedEditText) view.findViewById(R.id.backup_code_input);
mCodeEditText.setInputType(
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
setupAutomaticLinebreak(mCodeEditText);
mCodeEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
mCodeEditText = new EditText[6];
mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
mCodeEditText[4] = (EditText) view.findViewById(R.id.backup_code_5);
mCodeEditText[5] = (EditText) view.findViewById(R.id.backup_code_6);
{
TextView[] codeDisplayText = new TextView[6];
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);
codeDisplayText[4] = (TextView) view.findViewById(R.id.backup_code_display_5);
codeDisplayText[5] = (TextView) view.findViewById(R.id.backup_code_display_6);
// set backup code in code TextViews
char[] backupCode = mBackupCode.toCharArray();
for (int i = 0; i < codeDisplayText.length; i++) {
codeDisplayText[i].setText(backupCode, i * 5, 4);
}
// 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);
// prevent selection action mode, partially circumventing text selection bug
mCodeEditText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
});
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);
mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator);
mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator);
@@ -347,67 +346,76 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel);
}
/**
* Automatic line break with max 6 lines for smaller displays
* <p/>
* 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
*/
private void setupAutomaticLinebreak(TextView textview) {
textview.setSingleLine(true);
textview.setMaxLines(6);
textview.setHorizontallyScrolling(false);
}
private void setupEditTextSuccessListener(final EditText[] backupCodes) {
for (EditText backupCode : backupCodes) {
private void setupEditTextSuccessListener(final MaskedEditText backupCode) {
backupCode.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
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) {
String currentBackupCode = backupCode.getText().toString();
boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
|| mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
boolean partIsComplete = (currentBackupCode.indexOf(' ') == -1);
if (!inInputState || !partIsComplete) {
return;
}
checkIfCodeIsCorrect(currentBackupCode);
}
});
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.length() > 4) {
throw new AssertionError("max length of each field is 4!");
}
boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
|| mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
boolean partIsComplete = s.length() == 4;
if (!inInputState || !partIsComplete) {
return;
}
checkIfCodeIsCorrect();
}
});
}
}
private void checkIfCodeIsCorrect(String currentBackupCode) {
private void checkIfCodeIsCorrect() {
if (Constants.DEBUG && mDebugModeAcceptAnyCode) {
switchState(BackupCodeState.STATE_OK, true);
return;
}
if (currentBackupCode.equals(mBackupCode)) {
StringBuilder backupCodeInput = new StringBuilder(26);
for (EditText editText : mCodeEditText) {
if (editText.getText().length() < 4) {
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);
return;
}
switchState(BackupCodeState.STATE_INPUT_ERROR, true);
}
private static void animateFlashText(
final TextView textView, int color1, int color2, boolean staySecondColor) {
final TextView[] textViews, int color1, int color2, boolean staySecondColor) {
ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
textView.setTextColor((Integer) animator.getAnimatedValue());
for (TextView textView : textViews) {
textView.setTextColor((Integer) animator.getAnimatedValue());
}
}
});
anim.setRepeatMode(ValueAnimator.REVERSE);
@@ -418,6 +426,34 @@ 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) == 4;
if (inserting && cursorAtEnd) {
backupCodes[next].requestFocus();
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
}
private void pushBackStackEntry() {
if (mBackStackLevel != null) {
return;

View File

@@ -46,8 +46,6 @@ import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewAnimator;
import com.github.pinball83.maskededittext.MaskedEditText;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
@@ -60,7 +58,6 @@ import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -157,7 +154,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
private EditText mPassphraseEditText;
private TextView mPassphraseText;
private MaskedEditText mBackupCodeEditText;
private EditText[] mBackupCodeEditText;
private boolean mIsCancelled = false;
private RequiredInputParcel mRequiredInput;
@@ -184,15 +181,15 @@ public class PassphraseDialogActivity extends FragmentActivity {
View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null);
alert.setView(view);
mBackupCodeEditText = (MaskedEditText) view.findViewById(R.id.backup_code);
// NOTE: order of these method calls matter, see setupAutomaticLinebreak()
mBackupCodeEditText.setInputType(
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
setupAutomaticLinebreak(mBackupCodeEditText);
mBackupCodeEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
mBackupCodeEditText.setOnEditorActionListener(this);
mBackupCodeEditText = new EditText[6];
mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
mBackupCodeEditText[4] = (EditText) view.findViewById(R.id.backup_code_5);
mBackupCodeEditText[5] = (EditText) view.findViewById(R.id.backup_code_6);
openKeyboard(mBackupCodeEditText);
setupEditTextFocusNext(mBackupCodeEditText);
AlertDialog dialog = alert.create();
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
@@ -307,11 +304,11 @@ public class PassphraseDialogActivity extends FragmentActivity {
/**
* Hack to open keyboard.
*
* This is the only method that I found to work across all Android versions
* http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
* Notes: * onCreateView can't be used because we want to add buttons to the dialog
* * opening in onActivityCreated does not work on Android 4.4
* Notes:
* * onCreateView can't be used because we want to add buttons to the dialog
* * opening in onActivityCreated does not work on Android 4.4
*/
private void openKeyboard(final TextView textView) {
textView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@@ -333,18 +330,35 @@ public class PassphraseDialogActivity extends FragmentActivity {
textView.requestFocus();
}
/**
* Automatic line break with max 6 lines for smaller displays
* <p/>
* 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
*/
private void setupAutomaticLinebreak(TextView textview) {
textview.setSingleLine(true);
textview.setMaxLines(6);
textview.setHorizontallyScrolling(false);
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) == 4;
if (inserting && cursorAtEnd) {
backupCodes[next].requestFocus();
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
}
@Override
public void onStart() {
super.onStart();
@@ -356,8 +370,17 @@ public class PassphraseDialogActivity extends FragmentActivity {
public void onClick(View v) {
if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) {
Passphrase passphrase =
new Passphrase(mBackupCodeEditText.getText().toString());
StringBuilder backupCodeInput = new StringBuilder(26);
for (EditText editText : mBackupCodeEditText) {
if (editText.getText().length() < 4) {
return;
}
backupCodeInput.append(editText.getText());
backupCodeInput.append('-');
}
backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
Passphrase passphrase = new Passphrase(backupCodeInput.toString());
finishCaching(passphrase);
return;