2018-02-13 21:17:21 +01:00
|
|
|
package me.impy.aegis.ui;
|
2017-12-27 22:04:22 +01:00
|
|
|
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.os.Bundle;
|
2017-12-27 23:01:23 +01:00
|
|
|
import android.support.annotation.ArrayRes;
|
2017-12-27 22:04:22 +01:00
|
|
|
import android.support.v7.app.ActionBar;
|
|
|
|
import android.support.v7.app.AlertDialog;
|
|
|
|
import android.text.Editable;
|
|
|
|
import android.text.TextWatcher;
|
|
|
|
import android.view.Menu;
|
|
|
|
import android.view.MenuItem;
|
|
|
|
import android.view.MotionEvent;
|
|
|
|
import android.view.View;
|
2018-04-05 00:07:48 +02:00
|
|
|
import android.view.animation.AccelerateInterpolator;
|
|
|
|
import android.view.animation.AlphaAnimation;
|
|
|
|
import android.view.animation.Animation;
|
2017-12-27 22:04:22 +01:00
|
|
|
import android.widget.AdapterView;
|
|
|
|
import android.widget.EditText;
|
2018-04-10 13:35:35 +02:00
|
|
|
import android.widget.ExpandableListAdapter;
|
2017-12-27 22:04:22 +01:00
|
|
|
import android.widget.ImageView;
|
2018-04-05 00:07:48 +02:00
|
|
|
import android.widget.RelativeLayout;
|
2017-12-27 22:04:22 +01:00
|
|
|
import android.widget.Spinner;
|
|
|
|
|
2018-01-02 21:50:07 +01:00
|
|
|
import com.amulyakhare.textdrawable.TextDrawable;
|
|
|
|
|
2018-02-13 21:17:21 +01:00
|
|
|
import me.impy.aegis.R;
|
2017-12-27 22:04:22 +01:00
|
|
|
import me.impy.aegis.crypto.KeyInfo;
|
2018-01-01 22:14:11 +01:00
|
|
|
import me.impy.aegis.crypto.KeyInfoException;
|
2017-12-27 22:04:22 +01:00
|
|
|
import me.impy.aegis.db.DatabaseEntry;
|
|
|
|
import me.impy.aegis.encoding.Base32;
|
2018-02-13 21:17:21 +01:00
|
|
|
import me.impy.aegis.helpers.EditTextHelper;
|
2017-12-27 22:04:22 +01:00
|
|
|
import me.impy.aegis.helpers.SpinnerHelper;
|
2018-01-02 21:50:07 +01:00
|
|
|
import me.impy.aegis.helpers.TextDrawableHelper;
|
2018-02-13 21:17:21 +01:00
|
|
|
import me.impy.aegis.ui.views.KeyProfile;
|
2017-12-27 22:04:22 +01:00
|
|
|
|
|
|
|
public class EditProfileActivity extends AegisActivity {
|
2017-12-30 14:21:21 +01:00
|
|
|
private boolean _isNew = false;
|
2017-12-27 22:04:22 +01:00
|
|
|
private boolean _edited = false;
|
|
|
|
private KeyProfile _profile;
|
|
|
|
|
2018-01-02 21:50:07 +01:00
|
|
|
private ImageView _iconView;
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
private EditText _textName;
|
|
|
|
private EditText _textIssuer;
|
|
|
|
private EditText _textPeriod;
|
|
|
|
private EditText _textSecret;
|
|
|
|
|
|
|
|
private Spinner _spinnerType;
|
|
|
|
private Spinner _spinnerAlgo;
|
|
|
|
private Spinner _spinnerDigits;
|
|
|
|
private SpinnerItemSelectedListener _selectedListener = new SpinnerItemSelectedListener();
|
|
|
|
|
2018-04-10 13:35:35 +02:00
|
|
|
private RelativeLayout _advancedSettingsHeader;
|
|
|
|
private RelativeLayout _advancedSettings;
|
|
|
|
|
2018-04-10 00:45:48 +02:00
|
|
|
int _dialogStyle = android.R.style.Theme_Material_Light_Dialog_NoActionBar;
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
@Override
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
setContentView(R.layout.activity_edit_profile);
|
|
|
|
|
|
|
|
ActionBar bar = getSupportActionBar();
|
|
|
|
bar.setHomeAsUpIndicator(R.drawable.ic_close);
|
|
|
|
bar.setDisplayHomeAsUpEnabled(true);
|
|
|
|
|
2017-12-30 14:21:21 +01:00
|
|
|
// if the intent doesn't contain a KeyProfile, create a new one
|
2018-01-02 21:50:07 +01:00
|
|
|
Intent intent = getIntent();
|
|
|
|
_profile = (KeyProfile) intent.getSerializableExtra("KeyProfile");
|
|
|
|
_isNew = intent.getBooleanExtra("isNew", false);
|
2017-12-30 00:26:16 +01:00
|
|
|
if (_profile == null) {
|
|
|
|
_profile = new KeyProfile();
|
2018-01-02 21:50:07 +01:00
|
|
|
}
|
|
|
|
if (_isNew) {
|
2017-12-30 00:26:16 +01:00
|
|
|
setTitle("Add profile");
|
|
|
|
}
|
|
|
|
|
2018-01-02 21:50:07 +01:00
|
|
|
_iconView = findViewById(R.id.profile_drawable);
|
2017-12-30 00:26:16 +01:00
|
|
|
_textName = findViewById(R.id.text_name);
|
|
|
|
_textIssuer = findViewById(R.id.text_issuer);
|
|
|
|
_textPeriod = findViewById(R.id.text_period);
|
|
|
|
_textSecret = findViewById(R.id.text_secret);
|
|
|
|
_spinnerType = findViewById(R.id.spinner_type);
|
|
|
|
SpinnerHelper.fillSpinner(this, _spinnerType, R.array.otp_types_array);
|
|
|
|
_spinnerAlgo = findViewById(R.id.spinner_algo);
|
|
|
|
SpinnerHelper.fillSpinner(this, _spinnerAlgo, R.array.otp_algo_array);
|
|
|
|
_spinnerDigits = findViewById(R.id.spinner_digits);
|
|
|
|
SpinnerHelper.fillSpinner(this, _spinnerDigits, R.array.otp_digits_array);
|
|
|
|
|
2018-04-10 13:35:35 +02:00
|
|
|
_advancedSettingsHeader = findViewById(R.id.accordian_header);
|
|
|
|
_advancedSettings = findViewById(R.id.expandableLayout);
|
|
|
|
|
2017-12-30 00:26:16 +01:00
|
|
|
updateFields();
|
|
|
|
|
|
|
|
_textName.addTextChangedListener(_textListener);
|
|
|
|
_textIssuer.addTextChangedListener(_textListener);
|
|
|
|
_textPeriod.addTextChangedListener(_textListener);
|
|
|
|
_textSecret.addTextChangedListener(_textListener);
|
|
|
|
_spinnerType.setOnTouchListener(_selectedListener);
|
|
|
|
_spinnerType.setOnItemSelectedListener(_selectedListener);
|
|
|
|
_spinnerAlgo.setOnTouchListener(_selectedListener);
|
|
|
|
_spinnerAlgo.setOnItemSelectedListener(_selectedListener);
|
|
|
|
_spinnerDigits.setOnTouchListener(_selectedListener);
|
|
|
|
_spinnerDigits.setOnItemSelectedListener(_selectedListener);
|
2018-01-02 21:50:07 +01:00
|
|
|
|
|
|
|
// update the icon if the text changed
|
|
|
|
_textName.addTextChangedListener(new TextWatcher() {
|
|
|
|
@Override
|
2018-04-10 13:35:35 +02:00
|
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
|
|
}
|
|
|
|
|
2018-01-02 21:50:07 +01:00
|
|
|
@Override
|
2018-04-10 13:35:35 +02:00
|
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
|
|
}
|
|
|
|
|
2018-01-02 21:50:07 +01:00
|
|
|
@Override
|
|
|
|
public void afterTextChanged(Editable s) {
|
|
|
|
TextDrawable drawable = TextDrawableHelper.generate(s.toString());
|
|
|
|
_iconView.setImageDrawable(drawable);
|
|
|
|
}
|
|
|
|
});
|
2018-04-05 00:07:48 +02:00
|
|
|
|
2018-04-10 13:35:35 +02:00
|
|
|
_advancedSettingsHeader.setOnClickListener(v -> {
|
|
|
|
OpenAdvancedSettings();
|
2018-04-05 00:07:48 +02:00
|
|
|
});
|
2018-04-10 13:35:35 +02:00
|
|
|
|
|
|
|
// Automatically open advanced settings since 'Secret' is required.
|
|
|
|
if(_isNew){
|
|
|
|
OpenAdvancedSettings();
|
|
|
|
}
|
2017-12-30 00:26:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void updateFields() {
|
|
|
|
DatabaseEntry entry = _profile.getEntry();
|
2018-01-02 21:50:07 +01:00
|
|
|
_iconView.setImageDrawable(_profile.getDrawable());
|
2017-12-27 22:04:22 +01:00
|
|
|
|
|
|
|
_textName.setText(entry.getName());
|
|
|
|
_textIssuer.setText(entry.getInfo().getIssuer());
|
|
|
|
_textPeriod.setText(Integer.toString(entry.getInfo().getPeriod()));
|
|
|
|
|
2017-12-30 00:26:16 +01:00
|
|
|
byte[] secretBytes = entry.getInfo().getSecret();
|
|
|
|
if (secretBytes != null) {
|
2018-01-01 22:14:11 +01:00
|
|
|
char[] secretChars = Base32.encode(secretBytes);
|
|
|
|
_textSecret.setText(secretChars, 0, secretChars.length);
|
2017-12-30 00:26:16 +01:00
|
|
|
}
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2017-12-27 23:01:23 +01:00
|
|
|
String type = entry.getInfo().getType();
|
|
|
|
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type), false);
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2017-12-27 23:01:23 +01:00
|
|
|
String algo = entry.getInfo().getAlgorithm(false);
|
|
|
|
_spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false);
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2017-12-27 23:01:23 +01:00
|
|
|
String digits = Integer.toString(entry.getInfo().getDigits());
|
|
|
|
_spinnerDigits.setSelection(getStringResourceIndex(R.array.otp_digits_array, digits), false);
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void setPreferredTheme(boolean nightMode) {
|
|
|
|
if (nightMode) {
|
2018-04-10 00:45:48 +02:00
|
|
|
_dialogStyle = android.R.style.Theme_Material_Dialog_NoActionBar;
|
2017-12-27 22:04:22 +01:00
|
|
|
setTheme(R.style.AppTheme_Dark_TransparentActionBar);
|
|
|
|
} else {
|
2018-04-10 00:45:48 +02:00
|
|
|
_dialogStyle = android.R.style.Theme_Material_Light_Dialog_NoActionBar;
|
2017-12-27 22:04:22 +01:00
|
|
|
setTheme(R.style.AppTheme_Default_TransparentActionBar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-10 13:35:35 +02:00
|
|
|
private void OpenAdvancedSettings() {
|
|
|
|
Animation fadeOut = new AlphaAnimation(1, 0); // the 1, 0 here notifies that we want the opacity to go from opaque (1) to transparent (0)
|
|
|
|
fadeOut.setInterpolator(new AccelerateInterpolator());
|
|
|
|
fadeOut.setDuration(220); // Fadeout duration should be 1000 milli seconds
|
|
|
|
_advancedSettingsHeader.startAnimation(fadeOut);
|
|
|
|
|
|
|
|
Animation fadeIn = new AlphaAnimation(0, 1); // the 1, 0 here notifies that we want the opacity to go from opaque (1) to transparent (0)
|
|
|
|
fadeIn.setInterpolator(new AccelerateInterpolator());
|
|
|
|
fadeIn.setDuration(250); // Fadeout duration should be 1000 milli seconds
|
|
|
|
|
|
|
|
fadeOut.setAnimationListener(new Animation.AnimationListener() {
|
|
|
|
@Override
|
|
|
|
public void onAnimationStart(Animation animation) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onAnimationEnd(Animation animation) {
|
|
|
|
_advancedSettingsHeader.setVisibility(View.GONE);
|
|
|
|
_advancedSettings.startAnimation(fadeIn);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onAnimationRepeat(Animation animation) {
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
fadeIn.setAnimationListener(new Animation.AnimationListener() {
|
|
|
|
@Override
|
|
|
|
public void onAnimationStart(Animation animation) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onAnimationEnd(Animation animation) {
|
|
|
|
_advancedSettings.setVisibility(View.VISIBLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onAnimationRepeat(Animation animation) {
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
@Override
|
|
|
|
public void onBackPressed() {
|
|
|
|
if (!_edited) {
|
|
|
|
super.onBackPressed();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-10 00:45:48 +02:00
|
|
|
new AlertDialog.Builder(this, _dialogStyle)
|
|
|
|
.setTitle("Discard changes?")
|
2017-12-27 22:04:22 +01:00
|
|
|
.setMessage("Your changes have not been saved")
|
|
|
|
.setPositiveButton(R.string.save, (dialog, which) -> onSave())
|
|
|
|
.setNegativeButton(R.string.discard, (dialog, which) -> super.onBackPressed())
|
|
|
|
.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
|
|
switch (item.getItemId()) {
|
|
|
|
case android.R.id.home:
|
|
|
|
onBackPressed();
|
|
|
|
return true;
|
|
|
|
case R.id.action_save:
|
|
|
|
return onSave();
|
|
|
|
case R.id.action_delete:
|
|
|
|
return onDelete();
|
|
|
|
default:
|
|
|
|
return super.onOptionsItemSelected(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
|
|
getMenuInflater().inflate(R.menu.menu_edit, menu);
|
2017-12-30 14:21:21 +01:00
|
|
|
if (_isNew) {
|
|
|
|
menu.findItem(R.id.action_delete).setVisible(false);
|
|
|
|
}
|
2017-12-27 22:04:22 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-01 22:53:10 +01:00
|
|
|
private void finish(boolean delete) {
|
|
|
|
Intent intent = new Intent();
|
|
|
|
intent.putExtra("KeyProfile", _profile);
|
|
|
|
intent.putExtra("delete", delete);
|
|
|
|
setResult(RESULT_OK, intent);
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
private boolean onDelete() {
|
2018-01-01 22:53:10 +01:00
|
|
|
finish(true);
|
|
|
|
return true;
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private boolean onSave() {
|
2018-01-01 22:14:11 +01:00
|
|
|
if (_textSecret.length() == 0) {
|
|
|
|
onError("Secret is a required field.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
int period;
|
|
|
|
try {
|
|
|
|
period = Integer.parseInt(_textPeriod.getText().toString());
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
onError("Period is not an integer.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
String type = _spinnerType.getSelectedItem().toString();
|
|
|
|
String algo = _spinnerAlgo.getSelectedItem().toString();
|
|
|
|
|
|
|
|
int digits;
|
|
|
|
try {
|
|
|
|
digits = Integer.parseInt(_spinnerDigits.getSelectedItem().toString());
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
onError("Digits is not an integer.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DatabaseEntry entry = _profile.getEntry();
|
|
|
|
entry.setName(_textName.getText().toString());
|
|
|
|
KeyInfo info = entry.getInfo();
|
2018-01-01 22:14:11 +01:00
|
|
|
|
|
|
|
try {
|
2018-02-13 21:17:21 +01:00
|
|
|
char[] secret = EditTextHelper.getEditTextChars(_textSecret);
|
2018-01-01 22:14:11 +01:00
|
|
|
info.setSecret(secret);
|
|
|
|
info.setIssuer(_textIssuer.getText().toString());
|
|
|
|
info.setPeriod(period);
|
|
|
|
info.setDigits(digits);
|
|
|
|
info.setAlgorithm(algo);
|
|
|
|
info.setType(type);
|
2018-04-10 12:48:48 +02:00
|
|
|
info.setAccountName(_textName.getText().toString());
|
2018-01-01 22:14:11 +01:00
|
|
|
} catch (KeyInfoException e) {
|
|
|
|
onError("The entered info is incorrect: " + e.getMessage());
|
|
|
|
return false;
|
|
|
|
}
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2018-01-01 22:53:10 +01:00
|
|
|
finish(false);
|
2017-12-27 22:04:22 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onError(String msg) {
|
|
|
|
new AlertDialog.Builder(this)
|
|
|
|
.setTitle("Error saving profile")
|
|
|
|
.setMessage(msg)
|
|
|
|
.setPositiveButton(android.R.string.ok, null)
|
|
|
|
.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onFieldEdited() {
|
|
|
|
_edited = true;
|
|
|
|
}
|
|
|
|
|
2017-12-30 00:26:16 +01:00
|
|
|
private TextWatcher _textListener = new TextWatcher() {
|
2017-12-27 22:04:22 +01:00
|
|
|
@Override
|
|
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
|
|
onFieldEdited();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
|
|
onFieldEdited();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void afterTextChanged(Editable s) {
|
|
|
|
onFieldEdited();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private class SpinnerItemSelectedListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {
|
|
|
|
private boolean _userSelect = false;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
|
|
_userSelect = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
|
|
if (_userSelect) {
|
|
|
|
onFieldEdited();
|
|
|
|
_userSelect = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onNothingSelected(AdapterView<?> parent) {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2017-12-27 23:01:23 +01:00
|
|
|
|
|
|
|
private int getStringResourceIndex(@ArrayRes int id, String string) {
|
|
|
|
String[] res = getResources().getStringArray(id);
|
|
|
|
for (int i = 0; i < res.length; i++) {
|
|
|
|
if (res[i].equals(string)) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|