2019-02-07 22:39:33 +01:00
|
|
|
package com.beemdevelopment.aegis.ui;
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2018-12-11 11:44:36 +01:00
|
|
|
import android.app.Activity;
|
2017-12-27 22:04:22 +01:00
|
|
|
import android.content.Intent;
|
2018-12-11 11:44:36 +01:00
|
|
|
import android.content.res.Resources;
|
2018-06-06 21:26:09 +02:00
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.BitmapFactory;
|
2018-06-06 22:22:38 +02:00
|
|
|
import android.graphics.Canvas;
|
|
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
|
|
import android.graphics.drawable.Drawable;
|
2018-11-27 20:55:55 +01:00
|
|
|
import android.net.Uri;
|
2017-12-27 22:04:22 +01:00
|
|
|
import android.os.Bundle;
|
2018-09-25 16:26:57 +02:00
|
|
|
import androidx.annotation.ArrayRes;
|
|
|
|
import androidx.appcompat.app.ActionBar;
|
2018-09-25 19:36:56 +02:00
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
import android.text.Editable;
|
|
|
|
import android.text.TextWatcher;
|
|
|
|
import android.view.Menu;
|
|
|
|
import android.view.MenuItem;
|
|
|
|
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;
|
|
|
|
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-06-06 16:15:31 +02:00
|
|
|
import android.widget.TableRow;
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2018-01-02 21:50:07 +01:00
|
|
|
import com.amulyakhare.textdrawable.TextDrawable;
|
2018-06-06 21:26:09 +02:00
|
|
|
import com.avito.android.krop.KropView;
|
2019-02-07 22:39:33 +01:00
|
|
|
import com.beemdevelopment.aegis.encoding.Base32;
|
|
|
|
import com.beemdevelopment.aegis.encoding.Base32Exception;
|
|
|
|
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
|
|
|
import com.beemdevelopment.aegis.helpers.SpinnerHelper;
|
|
|
|
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
|
|
|
|
import com.beemdevelopment.aegis.otp.HotpInfo;
|
|
|
|
import com.beemdevelopment.aegis.otp.OtpInfo;
|
|
|
|
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
|
|
|
import com.beemdevelopment.aegis.otp.TotpInfo;
|
2018-01-02 21:50:07 +01:00
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
import java.io.ByteArrayInputStream;
|
2018-06-06 22:22:38 +02:00
|
|
|
import java.io.ByteArrayOutputStream;
|
2018-11-27 20:55:55 +01:00
|
|
|
import java.io.FileNotFoundException;
|
2018-09-25 19:36:56 +02:00
|
|
|
import java.io.IOException;
|
2018-11-27 20:55:55 +01:00
|
|
|
import java.io.InputStream;
|
2018-09-25 19:36:56 +02:00
|
|
|
import java.io.ObjectInputStream;
|
|
|
|
import java.io.ObjectOutputStream;
|
2018-12-11 11:44:36 +01:00
|
|
|
import java.text.Collator;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.TreeSet;
|
2018-09-25 19:36:56 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
2018-06-06 22:22:38 +02:00
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
import androidx.appcompat.app.AlertDialog;
|
2018-06-06 21:57:38 +02:00
|
|
|
import de.hdodenhof.circleimageview.CircleImageView;
|
2019-02-07 22:39:33 +01:00
|
|
|
import com.beemdevelopment.aegis.R;
|
|
|
|
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
public class EditEntryActivity extends AegisActivity {
|
2018-11-27 20:55:55 +01:00
|
|
|
private static final int PICK_IMAGE_REQUEST = 0;
|
|
|
|
|
2017-12-30 14:21:21 +01:00
|
|
|
private boolean _isNew = false;
|
2018-09-25 19:36:56 +02:00
|
|
|
private DatabaseEntry _origEntry;
|
2018-12-11 11:44:36 +01:00
|
|
|
private TreeSet<String> _groups;
|
2018-09-25 19:36:56 +02:00
|
|
|
private boolean _hasCustomIcon = false;
|
|
|
|
// keep track of icon changes separately as the generated jpeg's are not deterministic
|
|
|
|
private boolean _hasChangedIcon = false;
|
2018-06-06 21:57:38 +02:00
|
|
|
private CircleImageView _iconView;
|
|
|
|
private ImageView _saveImageButton;
|
2018-01-02 21:50:07 +01:00
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
private EditText _textName;
|
|
|
|
private EditText _textIssuer;
|
|
|
|
private EditText _textPeriod;
|
2018-06-06 16:15:31 +02:00
|
|
|
private EditText _textCounter;
|
2017-12-27 22:04:22 +01:00
|
|
|
private EditText _textSecret;
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
private TableRow _rowPeriod;
|
|
|
|
private TableRow _rowCounter;
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
private Spinner _spinnerType;
|
|
|
|
private Spinner _spinnerAlgo;
|
|
|
|
private Spinner _spinnerDigits;
|
2018-12-11 11:44:36 +01:00
|
|
|
private Spinner _spinnerGroup;
|
|
|
|
private List<String> _spinnerGroupList = new ArrayList<>();
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2018-06-06 21:26:09 +02:00
|
|
|
private KropView _kropView;
|
|
|
|
|
2018-04-10 13:35:35 +02:00
|
|
|
private RelativeLayout _advancedSettingsHeader;
|
|
|
|
private RelativeLayout _advancedSettings;
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
@Override
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
super.onCreate(savedInstanceState);
|
2018-06-06 16:15:31 +02:00
|
|
|
setContentView(R.layout.activity_edit_entry);
|
2017-12-27 22:04:22 +01:00
|
|
|
|
|
|
|
ActionBar bar = getSupportActionBar();
|
|
|
|
bar.setHomeAsUpIndicator(R.drawable.ic_close);
|
|
|
|
bar.setDisplayHomeAsUpEnabled(true);
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
// retrieve info from the calling activity
|
2018-01-02 21:50:07 +01:00
|
|
|
Intent intent = getIntent();
|
2018-09-25 19:36:56 +02:00
|
|
|
_origEntry = (DatabaseEntry) intent.getSerializableExtra("entry");
|
2018-01-02 21:50:07 +01:00
|
|
|
_isNew = intent.getBooleanExtra("isNew", false);
|
2018-12-18 22:46:35 +01:00
|
|
|
_groups = new TreeSet<>(Collator.getInstance());
|
2018-12-11 11:44:36 +01:00
|
|
|
_groups.addAll(intent.getStringArrayListExtra("groups"));
|
2018-01-02 21:50:07 +01:00
|
|
|
if (_isNew) {
|
2018-10-09 23:13:51 +02:00
|
|
|
setTitle(R.string.add_new_profile);
|
2017-12-30 00:26:16 +01:00
|
|
|
}
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
// set up fields
|
2018-01-02 21:50:07 +01:00
|
|
|
_iconView = findViewById(R.id.profile_drawable);
|
2018-06-06 21:26:09 +02:00
|
|
|
_kropView = findViewById(R.id.krop_view);
|
2018-06-06 21:57:38 +02:00
|
|
|
_saveImageButton = findViewById(R.id.iv_saveImage);
|
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);
|
2018-06-06 16:15:31 +02:00
|
|
|
_rowPeriod = findViewById(R.id.row_period);
|
|
|
|
_textCounter = findViewById(R.id.text_counter);
|
|
|
|
_rowCounter = findViewById(R.id.row_counter);
|
2017-12-30 00:26:16 +01:00
|
|
|
_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-12-11 11:44:36 +01:00
|
|
|
_spinnerGroup = findViewById(R.id.spinner_group);
|
|
|
|
updateGroupSpinnerList();
|
|
|
|
SpinnerHelper.fillSpinner(this, _spinnerGroup, _spinnerGroupList);
|
2018-06-09 20:23:39 +02:00
|
|
|
|
2018-04-10 13:35:35 +02:00
|
|
|
_advancedSettingsHeader = findViewById(R.id.accordian_header);
|
|
|
|
_advancedSettings = findViewById(R.id.expandableLayout);
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
// fill the fields with values if possible
|
2018-09-25 19:36:56 +02:00
|
|
|
if (_origEntry != null) {
|
|
|
|
if (_origEntry.hasIcon()) {
|
|
|
|
byte[] imageBytes = _origEntry.getIcon();
|
2018-06-07 14:33:33 +02:00
|
|
|
Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
|
|
|
|
_iconView.setImageBitmap(bitmap);
|
2018-09-25 19:36:56 +02:00
|
|
|
_hasCustomIcon = true;
|
2018-06-07 12:27:42 +02:00
|
|
|
} else {
|
2018-09-25 19:36:56 +02:00
|
|
|
TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView);
|
2018-06-07 12:27:42 +02:00
|
|
|
_iconView.setImageDrawable(drawable);
|
|
|
|
}
|
2018-06-06 16:15:31 +02:00
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
_textName.setText(_origEntry.getName());
|
|
|
|
_textIssuer.setText(_origEntry.getIssuer());
|
2018-06-06 16:15:31 +02:00
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
OtpInfo info = _origEntry.getInfo();
|
2018-06-06 16:15:31 +02:00
|
|
|
if (info instanceof TotpInfo) {
|
|
|
|
_textPeriod.setText(Integer.toString(((TotpInfo) info).getPeriod()));
|
|
|
|
_rowPeriod.setVisibility(View.VISIBLE);
|
|
|
|
} else if (info instanceof HotpInfo) {
|
|
|
|
_textCounter.setText(Long.toString(((HotpInfo) info).getCounter()));
|
|
|
|
_rowCounter.setVisibility(View.VISIBLE);
|
|
|
|
} else {
|
|
|
|
throw new RuntimeException();
|
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
byte[] secretBytes = _origEntry.getInfo().getSecret();
|
2018-06-06 16:15:31 +02:00
|
|
|
if (secretBytes != null) {
|
|
|
|
char[] secretChars = Base32.encode(secretBytes);
|
|
|
|
_textSecret.setText(secretChars, 0, secretChars.length);
|
|
|
|
}
|
2017-12-30 00:26:16 +01:00
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
String type = _origEntry.getInfo().getType();
|
2018-06-06 16:15:31 +02:00
|
|
|
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type.toUpperCase()), false);
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
String algo = _origEntry.getInfo().getAlgorithm(false);
|
2018-06-06 16:15:31 +02:00
|
|
|
_spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false);
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
String digits = Integer.toString(_origEntry.getInfo().getDigits());
|
2018-06-06 16:15:31 +02:00
|
|
|
_spinnerDigits.setSelection(getStringResourceIndex(R.array.otp_digits_array, digits), false);
|
2018-12-11 11:44:36 +01:00
|
|
|
|
|
|
|
String group = _origEntry.getGroup();
|
|
|
|
if (group != null) {
|
|
|
|
int pos = _groups.contains(group) ? _groups.headSet(group).size() : -1;
|
|
|
|
_spinnerGroup.setSelection(pos + 1, false);
|
|
|
|
}
|
2018-06-06 16:15:31 +02:00
|
|
|
}
|
|
|
|
|
2018-01-02 21:50:07 +01:00
|
|
|
// update the icon if the text changed
|
2018-06-06 16:27:56 +02:00
|
|
|
_textIssuer.addTextChangedListener(_iconChangeListener);
|
|
|
|
_textName.addTextChangedListener(_iconChangeListener);
|
2018-04-05 00:07:48 +02:00
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
// show/hide period and counter fields on type change
|
|
|
|
_spinnerType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
|
|
|
@Override
|
|
|
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
|
|
String type = _spinnerType.getSelectedItem().toString();
|
|
|
|
|
|
|
|
switch (type.toLowerCase()) {
|
|
|
|
case "totp":
|
|
|
|
_rowCounter.setVisibility(View.GONE);
|
|
|
|
_rowPeriod.setVisibility(View.VISIBLE);
|
|
|
|
break;
|
|
|
|
case "hotp":
|
|
|
|
_rowPeriod.setVisibility(View.GONE);
|
|
|
|
_rowCounter.setVisibility(View.VISIBLE);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new RuntimeException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onNothingSelected(AdapterView<?> parent) {
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-12-11 11:44:36 +01:00
|
|
|
final Activity activity = this;
|
|
|
|
_spinnerGroup.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
|
|
|
private int prevPosition;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
|
|
if (position == _spinnerGroupList.size() - 1) {
|
2018-12-16 22:25:50 +01:00
|
|
|
Dialogs.showCreateNewGroupDialog(activity, text -> {
|
|
|
|
if (text.isEmpty()) {
|
|
|
|
return;
|
2018-12-11 11:44:36 +01:00
|
|
|
}
|
2018-12-16 22:25:50 +01:00
|
|
|
_groups.add(text);
|
|
|
|
// reset the selection to "No group" to work around a quirk
|
|
|
|
_spinnerGroup.setSelection(0, false);
|
|
|
|
updateGroupSpinnerList();
|
|
|
|
_spinnerGroup.setSelection(_spinnerGroupList.indexOf(text), false);
|
2018-12-11 11:44:36 +01:00
|
|
|
});
|
|
|
|
_spinnerGroup.setSelection(prevPosition, false);
|
|
|
|
} else {
|
|
|
|
prevPosition = position;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onNothingSelected(AdapterView<?> parent) {
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-11-27 20:55:55 +01:00
|
|
|
_iconView.setOnClickListener(v -> {
|
|
|
|
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
|
|
|
|
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
|
2018-06-06 21:26:09 +02:00
|
|
|
|
2018-11-27 20:55:55 +01:00
|
|
|
Intent chooserIntent = Intent.createChooser(galleryIntent, "Select photo");
|
|
|
|
startActivityForResult(Intent.createChooser(chooserIntent, "Select photo"), PICK_IMAGE_REQUEST);
|
2018-04-05 00:07:48 +02:00
|
|
|
});
|
2018-04-10 13:35:35 +02:00
|
|
|
|
2018-11-27 20:55:55 +01:00
|
|
|
_advancedSettingsHeader.setOnClickListener(v -> openAdvancedSettings());
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
// automatically open advanced settings since 'Secret' is required.
|
2018-09-25 19:36:56 +02:00
|
|
|
if (_isNew) {
|
2018-05-11 15:12:36 +02:00
|
|
|
openAdvancedSettings();
|
2018-04-10 13:35:35 +02:00
|
|
|
}
|
2017-12-30 00:26:16 +01:00
|
|
|
}
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
@Override
|
2018-05-10 23:30:38 +02:00
|
|
|
protected void setPreferredTheme(boolean darkMode) {
|
|
|
|
if (darkMode) {
|
2018-09-18 21:29:11 +02:00
|
|
|
setTheme(R.style.AppTheme_Dark);
|
2017-12-27 22:04:22 +01:00
|
|
|
} else {
|
2018-09-18 21:29:11 +02:00
|
|
|
setTheme(R.style.AppTheme);
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-11 15:12:36 +02:00
|
|
|
private void openAdvancedSettings() {
|
2018-09-25 19:36:56 +02:00
|
|
|
Animation fadeOut = new AlphaAnimation(1, 0);
|
2018-04-10 13:35:35 +02:00
|
|
|
fadeOut.setInterpolator(new AccelerateInterpolator());
|
2018-09-25 19:36:56 +02:00
|
|
|
fadeOut.setDuration(220);
|
2018-04-10 13:35:35 +02:00
|
|
|
_advancedSettingsHeader.startAnimation(fadeOut);
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
Animation fadeIn = new AlphaAnimation(0, 1);
|
2018-04-10 13:35:35 +02:00
|
|
|
fadeIn.setInterpolator(new AccelerateInterpolator());
|
2018-09-25 19:36:56 +02:00
|
|
|
fadeIn.setDuration(250);
|
2018-04-10 13:35:35 +02:00
|
|
|
|
|
|
|
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) {
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-12-11 11:44:36 +01:00
|
|
|
private void updateGroupSpinnerList() {
|
|
|
|
Resources res = getResources();
|
|
|
|
_spinnerGroupList.clear();
|
|
|
|
_spinnerGroupList.add(res.getString(R.string.no_group));
|
|
|
|
_spinnerGroupList.addAll(_groups);
|
|
|
|
_spinnerGroupList.add(res.getString(R.string.new_group));
|
|
|
|
}
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
@Override
|
|
|
|
public void onBackPressed() {
|
2018-09-25 19:36:56 +02:00
|
|
|
AtomicReference<String> msg = new AtomicReference<>();
|
|
|
|
AtomicReference<DatabaseEntry> entry = new AtomicReference<>();
|
|
|
|
|
|
|
|
try {
|
|
|
|
entry.set(parseEntry());
|
|
|
|
} catch (ParseException e) {
|
|
|
|
msg.set(e.getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
// close the activity if the entry has not been changed
|
|
|
|
if (_origEntry != null && !_hasChangedIcon && _origEntry.equals(entry.get())) {
|
2017-12-27 22:04:22 +01:00
|
|
|
super.onBackPressed();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
// ask for confirmation if the entry has been changed
|
2018-05-14 21:02:35 +02:00
|
|
|
Dialogs.showDiscardDialog(this,
|
2018-09-25 19:36:56 +02:00
|
|
|
(dialog, which) -> {
|
|
|
|
// if the entry couldn't be parsed, we show an error dialog
|
|
|
|
if (msg.get() != null) {
|
|
|
|
onSaveError(msg.get());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
finish(entry.get(), false);
|
|
|
|
},
|
2018-05-14 21:02:35 +02:00
|
|
|
(dialog, which) -> super.onBackPressed()
|
|
|
|
);
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
|
|
switch (item.getItemId()) {
|
|
|
|
case android.R.id.home:
|
|
|
|
onBackPressed();
|
2018-05-10 23:11:21 +02:00
|
|
|
break;
|
2017-12-27 22:04:22 +01:00
|
|
|
case R.id.action_save:
|
2018-05-10 23:11:21 +02:00
|
|
|
onSave();
|
|
|
|
break;
|
2017-12-27 22:04:22 +01:00
|
|
|
case R.id.action_delete:
|
2018-05-10 23:11:21 +02:00
|
|
|
Dialogs.showDeleteEntryDialog(this, (dialog, which) -> {
|
2018-09-25 19:36:56 +02:00
|
|
|
finish(_origEntry, true);
|
2018-05-10 23:11:21 +02:00
|
|
|
});
|
|
|
|
break;
|
2018-06-09 21:40:18 +02:00
|
|
|
case R.id.action_default_icon:
|
2018-09-25 19:36:56 +02:00
|
|
|
TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView);
|
2018-06-07 12:27:42 +02:00
|
|
|
_iconView.setImageDrawable(drawable);
|
2018-09-25 19:36:56 +02:00
|
|
|
_hasCustomIcon = false;
|
|
|
|
_hasChangedIcon = true;
|
2017-12-27 22:04:22 +01:00
|
|
|
default:
|
|
|
|
return super.onOptionsItemSelected(item);
|
|
|
|
}
|
2018-05-10 23:11:21 +02:00
|
|
|
|
|
|
|
return true;
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@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);
|
|
|
|
}
|
2018-09-25 19:36:56 +02:00
|
|
|
if (!_hasCustomIcon) {
|
2018-06-09 21:40:18 +02:00
|
|
|
menu.findItem(R.id.action_default_icon).setVisible(false);
|
2018-06-07 00:30:49 +02:00
|
|
|
}
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
private void finish(DatabaseEntry entry, boolean delete) {
|
2018-01-01 22:53:10 +01:00
|
|
|
Intent intent = new Intent();
|
2018-09-25 19:36:56 +02:00
|
|
|
intent.putExtra("entry", entry);
|
2018-01-01 22:53:10 +01:00
|
|
|
intent.putExtra("delete", delete);
|
|
|
|
setResult(RESULT_OK, intent);
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
2018-06-06 21:26:09 +02:00
|
|
|
@Override
|
|
|
|
protected void onActivityResult(int requestCode, final int resultCode, Intent data) {
|
2018-11-27 20:55:55 +01:00
|
|
|
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
|
|
|
|
Uri inputFile = (data.getData());
|
|
|
|
InputStream inputStream;
|
|
|
|
Bitmap bitmap;
|
|
|
|
try {
|
|
|
|
inputStream = getContentResolver().openInputStream(inputFile);
|
|
|
|
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
|
|
|
|
bitmap = BitmapFactory.decodeStream(inputStream, null, bmOptions);
|
|
|
|
_kropView.setBitmap(bitmap);
|
|
|
|
_kropView.setVisibility(View.VISIBLE);
|
|
|
|
} catch (FileNotFoundException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
2018-06-06 21:57:38 +02:00
|
|
|
|
|
|
|
_saveImageButton.setOnClickListener(new View.OnClickListener() {
|
|
|
|
@Override
|
|
|
|
public void onClick(View v) {
|
|
|
|
_iconView.setImageBitmap(_kropView.getCroppedBitmap());
|
|
|
|
_kropView.setVisibility(View.GONE);
|
2018-09-25 19:36:56 +02:00
|
|
|
_hasCustomIcon = true;
|
|
|
|
_hasChangedIcon = true;
|
2018-06-06 21:57:38 +02:00
|
|
|
}
|
|
|
|
});
|
2018-06-06 21:26:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
private DatabaseEntry parseEntry() throws ParseException {
|
2018-01-01 22:14:11 +01:00
|
|
|
if (_textSecret.length() == 0) {
|
2018-09-25 19:36:56 +02:00
|
|
|
throw new ParseException("Secret is a required field.");
|
2018-01-01 22:14:11 +01:00
|
|
|
}
|
|
|
|
|
2017-12-27 22:04:22 +01:00
|
|
|
String type = _spinnerType.getSelectedItem().toString();
|
|
|
|
String algo = _spinnerAlgo.getSelectedItem().toString();
|
|
|
|
|
|
|
|
int digits;
|
|
|
|
try {
|
|
|
|
digits = Integer.parseInt(_spinnerDigits.getSelectedItem().toString());
|
|
|
|
} catch (NumberFormatException e) {
|
2018-09-25 19:36:56 +02:00
|
|
|
throw new ParseException("Digits is not an integer.");
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
byte[] secret;
|
|
|
|
try {
|
|
|
|
secret = Base32.decode(EditTextHelper.getEditTextChars(_textSecret));
|
|
|
|
} catch (Base32Exception e) {
|
2018-09-25 19:36:56 +02:00
|
|
|
throw new ParseException("Secret is not valid base32.");
|
2018-06-06 16:15:31 +02:00
|
|
|
}
|
2018-01-01 22:14:11 +01:00
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
// set otp info
|
|
|
|
OtpInfo info;
|
2018-01-01 22:14:11 +01:00
|
|
|
try {
|
2018-06-06 16:15:31 +02:00
|
|
|
switch (type.toLowerCase()) {
|
|
|
|
case "totp":
|
|
|
|
int period;
|
|
|
|
try {
|
|
|
|
period = Integer.parseInt(_textPeriod.getText().toString());
|
|
|
|
} catch (NumberFormatException e) {
|
2018-09-25 19:36:56 +02:00
|
|
|
throw new ParseException("Period is not an integer.");
|
2018-06-06 16:15:31 +02:00
|
|
|
}
|
|
|
|
info = new TotpInfo(secret, algo, digits, period);
|
|
|
|
break;
|
|
|
|
case "hotp":
|
|
|
|
long counter;
|
|
|
|
try {
|
|
|
|
counter = Long.parseLong(_textCounter.getText().toString());
|
|
|
|
} catch (NumberFormatException e) {
|
2018-09-25 19:36:56 +02:00
|
|
|
throw new ParseException("Counter is not an integer.");
|
2018-06-06 16:15:31 +02:00
|
|
|
}
|
|
|
|
info = new HotpInfo(secret, algo, digits, counter);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new RuntimeException();
|
|
|
|
}
|
|
|
|
|
2018-01-01 22:14:11 +01:00
|
|
|
info.setDigits(digits);
|
|
|
|
info.setAlgorithm(algo);
|
2018-06-06 16:15:31 +02:00
|
|
|
} catch (OtpInfoException e) {
|
2018-09-25 19:36:56 +02:00
|
|
|
throw new ParseException("The entered info is incorrect: " + e.getMessage());
|
2018-01-01 22:14:11 +01:00
|
|
|
}
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
// set database entry info
|
2018-09-25 19:36:56 +02:00
|
|
|
DatabaseEntry entry;
|
|
|
|
if (_origEntry == null) {
|
2018-06-06 16:15:31 +02:00
|
|
|
entry = new DatabaseEntry(info);
|
|
|
|
} else {
|
2018-09-25 19:36:56 +02:00
|
|
|
entry = cloneEntry(_origEntry);
|
2018-06-06 16:15:31 +02:00
|
|
|
entry.setInfo(info);
|
|
|
|
}
|
|
|
|
entry.setIssuer(_textIssuer.getText().toString());
|
|
|
|
entry.setName(_textName.getText().toString());
|
|
|
|
|
2018-12-11 11:44:36 +01:00
|
|
|
int groupPos = _spinnerGroup.getSelectedItemPosition();
|
|
|
|
if (groupPos != 0) {
|
|
|
|
String group = _spinnerGroupList.get(_spinnerGroup.getSelectedItemPosition());
|
|
|
|
entry.setGroup(group);
|
|
|
|
} else {
|
|
|
|
entry.setGroup(null);
|
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
if (_hasChangedIcon) {
|
|
|
|
if (_hasCustomIcon) {
|
|
|
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
|
|
drawableToBitmap(_iconView.getDrawable()).compress(Bitmap.CompressFormat.JPEG, 100, stream);
|
|
|
|
byte[] data = stream.toByteArray();
|
|
|
|
entry.setIcon(data);
|
|
|
|
} else {
|
|
|
|
entry.setIcon(null);
|
|
|
|
}
|
2018-06-07 12:27:42 +02:00
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
return entry;
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
private void onSaveError(String msg) {
|
2018-11-15 22:20:31 +01:00
|
|
|
Dialogs.showSecureDialog(new AlertDialog.Builder(this)
|
2018-10-09 23:13:51 +02:00
|
|
|
.setTitle(getString(R.string.saving_profile_error))
|
2017-12-27 22:04:22 +01:00
|
|
|
.setMessage(msg)
|
|
|
|
.setPositiveButton(android.R.string.ok, null)
|
2018-11-15 22:20:31 +01:00
|
|
|
.create());
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
private boolean onSave() {
|
|
|
|
DatabaseEntry entry;
|
|
|
|
try {
|
|
|
|
entry = parseEntry();
|
|
|
|
} catch (ParseException e) {
|
|
|
|
onSaveError(e.getMessage());
|
|
|
|
return false;
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
finish(entry, false);
|
|
|
|
return true;
|
|
|
|
}
|
2017-12-27 22:04:22 +01:00
|
|
|
|
2018-06-06 16:27:56 +02:00
|
|
|
private TextWatcher _iconChangeListener = 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) {
|
2018-09-25 19:36:56 +02:00
|
|
|
if (!_hasCustomIcon) {
|
2018-06-07 12:27:42 +02:00
|
|
|
TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString(), _iconView);
|
|
|
|
_iconView.setImageDrawable(drawable);
|
|
|
|
}
|
2018-06-06 16:27:56 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
}
|
2018-06-06 22:22:38 +02:00
|
|
|
|
2018-09-25 19:36:56 +02:00
|
|
|
private static Bitmap drawableToBitmap(Drawable drawable) {
|
2018-06-06 22:22:38 +02:00
|
|
|
if (drawable instanceof BitmapDrawable) {
|
|
|
|
return ((BitmapDrawable) drawable).getBitmap();
|
|
|
|
}
|
|
|
|
|
|
|
|
final int width = !drawable.getBounds().isEmpty() ? drawable
|
|
|
|
.getBounds().width() : drawable.getIntrinsicWidth();
|
|
|
|
|
|
|
|
final int height = !drawable.getBounds().isEmpty() ? drawable
|
|
|
|
.getBounds().height() : drawable.getIntrinsicHeight();
|
|
|
|
|
|
|
|
final Bitmap bitmap = Bitmap.createBitmap(width <= 0 ? 1 : width,
|
|
|
|
height <= 0 ? 1 : height, Bitmap.Config.ARGB_8888);
|
|
|
|
|
|
|
|
Canvas canvas = new Canvas(bitmap);
|
|
|
|
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
|
|
|
drawable.draw(canvas);
|
|
|
|
|
|
|
|
return bitmap;
|
|
|
|
}
|
2018-09-25 19:36:56 +02:00
|
|
|
|
|
|
|
private static DatabaseEntry cloneEntry(DatabaseEntry entry) {
|
|
|
|
try {
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
|
|
|
oos.writeObject(entry);
|
|
|
|
|
|
|
|
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
|
|
|
ObjectInputStream ois = new ObjectInputStream(bais);
|
|
|
|
return (DatabaseEntry) ois.readObject();
|
|
|
|
} catch (ClassNotFoundException | IOException e) {
|
2018-09-25 20:05:08 +02:00
|
|
|
throw new RuntimeException(e);
|
2018-09-25 19:36:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class ParseException extends Exception {
|
|
|
|
public ParseException(String message) {
|
|
|
|
super(message);
|
|
|
|
}
|
|
|
|
}
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|