Improve database entry change detection in EditProfilActivity

This commit is contained in:
Alexander Bakker 2018-09-25 19:36:56 +02:00
parent 3435a4077e
commit 5f9559de75
5 changed files with 177 additions and 127 deletions

View file

@ -4,6 +4,7 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
import me.impy.aegis.encoding.Base64; import me.impy.aegis.encoding.Base64;
@ -107,4 +108,21 @@ public class DatabaseEntry implements Serializable {
public boolean hasIcon() { public boolean hasIcon() {
return _icon != null; return _icon != null;
} }
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DatabaseEntry)) {
return false;
}
DatabaseEntry entry = (DatabaseEntry) o;
return getUUID().equals(entry.getUUID())
&& getName().equals(entry.getName())
&& getIssuer().equals(entry.getIssuer())
&& getInfo().equals(entry.getInfo())
&& Arrays.equals(getIcon(), entry.getIcon());
}
} }

View file

@ -64,4 +64,14 @@ public class HotpInfo extends OtpInfo {
public void incrementCounter() throws OtpInfoException { public void incrementCounter() throws OtpInfoException {
setCounter(getCounter() + 1); setCounter(getCounter() + 1);
} }
@Override
public boolean equals(Object o) {
if (!(o instanceof HotpInfo)) {
return false;
}
HotpInfo info = (HotpInfo) o;
return super.equals(o) && getCounter() == info.getCounter();
}
} }

View file

@ -4,6 +4,7 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import me.impy.aegis.encoding.Base32; import me.impy.aegis.encoding.Base32;
import me.impy.aegis.encoding.Base32Exception; import me.impy.aegis.encoding.Base32Exception;
@ -113,4 +114,19 @@ public abstract class OtpInfo implements Serializable {
return info; return info;
} }
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof OtpInfo)) {
return false;
}
OtpInfo info = (OtpInfo) o;
return Arrays.equals(getSecret(), info.getSecret())
&& getAlgorithm(false).equals(info.getAlgorithm(false))
&& getDigits() == info.getDigits();
}
} }

View file

@ -63,4 +63,14 @@ public class TotpInfo extends OtpInfo {
long p = period * 1000; long p = period * 1000;
return p - (System.currentTimeMillis() % p); return p - (System.currentTimeMillis() % p);
} }
@Override
public boolean equals(Object o) {
if (!(o instanceof TotpInfo)) {
return false;
}
TotpInfo info = (TotpInfo) o;
return super.equals(o) && getPeriod() == info.getPeriod();
}
} }

View file

@ -10,12 +10,11 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.ArrayRes; import androidx.annotation.ArrayRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.animation.AccelerateInterpolator; import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation; import android.view.animation.AlphaAnimation;
@ -33,8 +32,14 @@ import com.esafirm.imagepicker.features.ImagePicker;
import com.esafirm.imagepicker.features.ReturnMode; import com.esafirm.imagepicker.features.ReturnMode;
import com.esafirm.imagepicker.model.Image; import com.esafirm.imagepicker.model.Image;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.concurrent.atomic.AtomicReference;
import androidx.appcompat.app.AlertDialog;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.db.DatabaseEntry; import me.impy.aegis.db.DatabaseEntry;
@ -51,9 +56,10 @@ import me.impy.aegis.ui.dialogs.Dialogs;
public class EditEntryActivity extends AegisActivity { public class EditEntryActivity extends AegisActivity {
private boolean _isNew = false; private boolean _isNew = false;
private boolean _edited = false; private DatabaseEntry _origEntry;
private DatabaseEntry _entry; private boolean _hasCustomIcon = false;
private boolean _hasCustomImage = false; // keep track of icon changes separately as the generated jpeg's are not deterministic
private boolean _hasChangedIcon = false;
private CircleImageView _iconView; private CircleImageView _iconView;
private ImageView _saveImageButton; private ImageView _saveImageButton;
@ -69,7 +75,6 @@ public class EditEntryActivity extends AegisActivity {
private Spinner _spinnerType; private Spinner _spinnerType;
private Spinner _spinnerAlgo; private Spinner _spinnerAlgo;
private Spinner _spinnerDigits; private Spinner _spinnerDigits;
private SpinnerItemSelectedListener _selectedListener;
private KropView _kropView; private KropView _kropView;
@ -87,7 +92,7 @@ public class EditEntryActivity extends AegisActivity {
// retrieve info from the calling activity // retrieve info from the calling activity
Intent intent = getIntent(); Intent intent = getIntent();
_entry = (DatabaseEntry) intent.getSerializableExtra("entry"); _origEntry = (DatabaseEntry) intent.getSerializableExtra("entry");
_isNew = intent.getBooleanExtra("isNew", false); _isNew = intent.getBooleanExtra("isNew", false);
if (_isNew) { if (_isNew) {
setTitle("Add profile"); setTitle("Add profile");
@ -110,27 +115,26 @@ public class EditEntryActivity extends AegisActivity {
SpinnerHelper.fillSpinner(this, _spinnerAlgo, R.array.otp_algo_array); SpinnerHelper.fillSpinner(this, _spinnerAlgo, R.array.otp_algo_array);
_spinnerDigits = findViewById(R.id.spinner_digits); _spinnerDigits = findViewById(R.id.spinner_digits);
SpinnerHelper.fillSpinner(this, _spinnerDigits, R.array.otp_digits_array); SpinnerHelper.fillSpinner(this, _spinnerDigits, R.array.otp_digits_array);
_selectedListener = new SpinnerItemSelectedListener();
_advancedSettingsHeader = findViewById(R.id.accordian_header); _advancedSettingsHeader = findViewById(R.id.accordian_header);
_advancedSettings = findViewById(R.id.expandableLayout); _advancedSettings = findViewById(R.id.expandableLayout);
// fill the fields with values if possible // fill the fields with values if possible
if (_entry != null) { if (_origEntry != null) {
if (_entry.hasIcon()) { if (_origEntry.hasIcon()) {
byte[] imageBytes = _entry.getIcon(); byte[] imageBytes = _origEntry.getIcon();
Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
_iconView.setImageBitmap(bitmap); _iconView.setImageBitmap(bitmap);
_hasCustomImage = true; _hasCustomIcon = true;
} else { } else {
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName(), _iconView); TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView);
_iconView.setImageDrawable(drawable); _iconView.setImageDrawable(drawable);
} }
_textName.setText(_entry.getName()); _textName.setText(_origEntry.getName());
_textIssuer.setText(_entry.getIssuer()); _textIssuer.setText(_origEntry.getIssuer());
OtpInfo info = _entry.getInfo(); OtpInfo info = _origEntry.getInfo();
if (info instanceof TotpInfo) { if (info instanceof TotpInfo) {
_textPeriod.setText(Integer.toString(((TotpInfo) info).getPeriod())); _textPeriod.setText(Integer.toString(((TotpInfo) info).getPeriod()));
_rowPeriod.setVisibility(View.VISIBLE); _rowPeriod.setVisibility(View.VISIBLE);
@ -141,33 +145,22 @@ public class EditEntryActivity extends AegisActivity {
throw new RuntimeException(); throw new RuntimeException();
} }
byte[] secretBytes = _entry.getInfo().getSecret(); byte[] secretBytes = _origEntry.getInfo().getSecret();
if (secretBytes != null) { if (secretBytes != null) {
char[] secretChars = Base32.encode(secretBytes); char[] secretChars = Base32.encode(secretBytes);
_textSecret.setText(secretChars, 0, secretChars.length); _textSecret.setText(secretChars, 0, secretChars.length);
} }
String type = _entry.getInfo().getType(); String type = _origEntry.getInfo().getType();
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type.toUpperCase()), false); _spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type.toUpperCase()), false);
String algo = _entry.getInfo().getAlgorithm(false); String algo = _origEntry.getInfo().getAlgorithm(false);
_spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false); _spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false);
String digits = Integer.toString(_entry.getInfo().getDigits()); String digits = Integer.toString(_origEntry.getInfo().getDigits());
_spinnerDigits.setSelection(getStringResourceIndex(R.array.otp_digits_array, digits), false); _spinnerDigits.setSelection(getStringResourceIndex(R.array.otp_digits_array, digits), false);
} }
// listen for changes to any of the fields
_textName.addTextChangedListener(_textListener);
_textIssuer.addTextChangedListener(_textListener);
_textPeriod.addTextChangedListener(_textListener);
_textCounter.addTextChangedListener(_textListener);
_textSecret.addTextChangedListener(_textListener);
_spinnerAlgo.setOnTouchListener(_selectedListener);
_spinnerAlgo.setOnItemSelectedListener(_selectedListener);
_spinnerDigits.setOnTouchListener(_selectedListener);
_spinnerDigits.setOnItemSelectedListener(_selectedListener);
// update the icon if the text changed // update the icon if the text changed
_textIssuer.addTextChangedListener(_iconChangeListener); _textIssuer.addTextChangedListener(_iconChangeListener);
_textName.addTextChangedListener(_iconChangeListener); _textName.addTextChangedListener(_iconChangeListener);
@ -190,8 +183,6 @@ public class EditEntryActivity extends AegisActivity {
default: default:
throw new RuntimeException(); throw new RuntimeException();
} }
_selectedListener.onItemSelected(parent, view, position, id);
} }
@Override @Override
@ -201,20 +192,21 @@ public class EditEntryActivity extends AegisActivity {
}); });
ImagePicker imagePicker = ImagePicker.create(this) ImagePicker imagePicker = ImagePicker.create(this)
.returnMode(ReturnMode.ALL) // set whether pick and / or camera action should return immediate result or not. .returnMode(ReturnMode.ALL)
.folderMode(true) // folder mode (false by default) .folderMode(true)
.toolbarFolderTitle("Folder") // folder selection title .toolbarFolderTitle("Folder")
.toolbarImageTitle("Tap to select") // image selection title .toolbarImageTitle("Tap to select")
.toolbarArrowColor(Color.BLACK) // Toolbar 'up' arrow color .toolbarArrowColor(Color.BLACK)
.single() // single mode .single()
.showCamera(false) // show camera or not (true by default) .showCamera(false)
.imageDirectory("Camera"); .imageDirectory("Camera");
// Open ImagePicker when clicking on the icon // open ImagePicker when clicking on the icon
_iconView.setOnClickListener(new View.OnClickListener() { _iconView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
imagePicker.start(); // start image picker activity with request code // start image picker activity with request code
imagePicker.start();
} }
}); });
@ -223,7 +215,7 @@ public class EditEntryActivity extends AegisActivity {
}); });
// automatically open advanced settings since 'Secret' is required. // automatically open advanced settings since 'Secret' is required.
if(_isNew){ if (_isNew) {
openAdvancedSettings(); openAdvancedSettings();
} }
} }
@ -238,14 +230,14 @@ public class EditEntryActivity extends AegisActivity {
} }
private void openAdvancedSettings() { 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) Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator()); fadeOut.setInterpolator(new AccelerateInterpolator());
fadeOut.setDuration(220); // Fadeout duration should be 1000 milli seconds fadeOut.setDuration(220);
_advancedSettingsHeader.startAnimation(fadeOut); _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) Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new AccelerateInterpolator()); fadeIn.setInterpolator(new AccelerateInterpolator());
fadeIn.setDuration(250); // Fadeout duration should be 1000 milli seconds fadeIn.setDuration(250);
fadeOut.setAnimationListener(new Animation.AnimationListener() { fadeOut.setAnimationListener(new Animation.AnimationListener() {
@Override @Override
@ -285,13 +277,32 @@ public class EditEntryActivity extends AegisActivity {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (!_edited) { 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())) {
super.onBackPressed(); super.onBackPressed();
return; return;
} }
// ask for confirmation if the entry has been changed
Dialogs.showDiscardDialog(this, Dialogs.showDiscardDialog(this,
(dialog, which) -> onSave(), (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);
},
(dialog, which) -> super.onBackPressed() (dialog, which) -> super.onBackPressed()
); );
} }
@ -307,13 +318,14 @@ public class EditEntryActivity extends AegisActivity {
break; break;
case R.id.action_delete: case R.id.action_delete:
Dialogs.showDeleteEntryDialog(this, (dialog, which) -> { Dialogs.showDeleteEntryDialog(this, (dialog, which) -> {
finish(true); finish(_origEntry, true);
}); });
break; break;
case R.id.action_default_icon: case R.id.action_default_icon:
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName(), _iconView); TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView);
_iconView.setImageDrawable(drawable); _iconView.setImageDrawable(drawable);
_hasCustomImage = false; _hasCustomIcon = false;
_hasChangedIcon = true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -327,16 +339,16 @@ public class EditEntryActivity extends AegisActivity {
if (_isNew) { if (_isNew) {
menu.findItem(R.id.action_delete).setVisible(false); menu.findItem(R.id.action_delete).setVisible(false);
} }
if (!_hasCustomImage) { if (!_hasCustomIcon) {
menu.findItem(R.id.action_default_icon).setVisible(false); menu.findItem(R.id.action_default_icon).setVisible(false);
} }
return true; return true;
} }
private void finish(boolean delete) { private void finish(DatabaseEntry entry, boolean delete) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra("entry", _entry); intent.putExtra("entry", entry);
intent.putExtra("delete", delete); intent.putExtra("delete", delete);
setResult(RESULT_OK, intent); setResult(RESULT_OK, intent);
finish(); finish();
@ -345,7 +357,6 @@ public class EditEntryActivity extends AegisActivity {
@Override @Override
protected void onActivityResult(int requestCode, final int resultCode, Intent data) { protected void onActivityResult(int requestCode, final int resultCode, Intent data) {
if (ImagePicker.shouldHandle(requestCode, resultCode, data)) { if (ImagePicker.shouldHandle(requestCode, resultCode, data)) {
// or get a single image only
Image image = ImagePicker.getFirstImageOrNull(data); Image image = ImagePicker.getFirstImageOrNull(data);
BitmapFactory.Options bmOptions = new BitmapFactory.Options(); BitmapFactory.Options bmOptions = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeFile(image.getPath(),bmOptions); Bitmap bitmap = BitmapFactory.decodeFile(image.getPath(),bmOptions);
@ -357,7 +368,8 @@ public class EditEntryActivity extends AegisActivity {
public void onClick(View v) { public void onClick(View v) {
_iconView.setImageBitmap(_kropView.getCroppedBitmap()); _iconView.setImageBitmap(_kropView.getCroppedBitmap());
_kropView.setVisibility(View.GONE); _kropView.setVisibility(View.GONE);
_hasCustomImage = true; _hasCustomIcon = true;
_hasChangedIcon = true;
} }
}); });
} }
@ -365,10 +377,9 @@ public class EditEntryActivity extends AegisActivity {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }
private boolean onSave() { private DatabaseEntry parseEntry() throws ParseException {
if (_textSecret.length() == 0) { if (_textSecret.length() == 0) {
onError("Secret is a required field."); throw new ParseException("Secret is a required field.");
return false;
} }
String type = _spinnerType.getSelectedItem().toString(); String type = _spinnerType.getSelectedItem().toString();
@ -378,16 +389,14 @@ public class EditEntryActivity extends AegisActivity {
try { try {
digits = Integer.parseInt(_spinnerDigits.getSelectedItem().toString()); digits = Integer.parseInt(_spinnerDigits.getSelectedItem().toString());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
onError("Digits is not an integer."); throw new ParseException("Digits is not an integer.");
return false;
} }
byte[] secret; byte[] secret;
try { try {
secret = Base32.decode(EditTextHelper.getEditTextChars(_textSecret)); secret = Base32.decode(EditTextHelper.getEditTextChars(_textSecret));
} catch (Base32Exception e) { } catch (Base32Exception e) {
onError("Secret is not valid base32."); throw new ParseException("Secret is not valid base32.");
return false;
} }
// set otp info // set otp info
@ -399,8 +408,7 @@ public class EditEntryActivity extends AegisActivity {
try { try {
period = Integer.parseInt(_textPeriod.getText().toString()); period = Integer.parseInt(_textPeriod.getText().toString());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
onError("Period is not an integer."); throw new ParseException("Period is not an integer.");
return false;
} }
info = new TotpInfo(secret, algo, digits, period); info = new TotpInfo(secret, algo, digits, period);
break; break;
@ -409,8 +417,7 @@ public class EditEntryActivity extends AegisActivity {
try { try {
counter = Long.parseLong(_textCounter.getText().toString()); counter = Long.parseLong(_textCounter.getText().toString());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
onError("Counter is not an integer."); throw new ParseException("Counter is not an integer.");
return false;
} }
info = new HotpInfo(secret, algo, digits, counter); info = new HotpInfo(secret, algo, digits, counter);
break; break;
@ -421,35 +428,35 @@ public class EditEntryActivity extends AegisActivity {
info.setDigits(digits); info.setDigits(digits);
info.setAlgorithm(algo); info.setAlgorithm(algo);
} catch (OtpInfoException e) { } catch (OtpInfoException e) {
onError("The entered info is incorrect: " + e.getMessage()); throw new ParseException("The entered info is incorrect: " + e.getMessage());
return false;
} }
// set database entry info // set database entry info
DatabaseEntry entry = _entry; DatabaseEntry entry;
if (entry == null) { if (_origEntry == null) {
entry = new DatabaseEntry(info); entry = new DatabaseEntry(info);
} else { } else {
entry = cloneEntry(_origEntry);
entry.setInfo(info); entry.setInfo(info);
} }
entry.setIssuer(_textIssuer.getText().toString()); entry.setIssuer(_textIssuer.getText().toString());
entry.setName(_textName.getText().toString()); entry.setName(_textName.getText().toString());
if (_hasCustomImage) { if (_hasChangedIcon) {
ByteArrayOutputStream stream = new ByteArrayOutputStream(); if (_hasCustomIcon) {
drawableToBitmap(_iconView.getDrawable()).compress(Bitmap.CompressFormat.JPEG, 100, stream); ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] bitmapdata = stream.toByteArray(); drawableToBitmap(_iconView.getDrawable()).compress(Bitmap.CompressFormat.JPEG, 100, stream);
entry.setIcon(bitmapdata); byte[] data = stream.toByteArray();
} else { entry.setIcon(data);
entry.setIcon(null); } else {
entry.setIcon(null);
}
} }
_entry = entry; return entry;
finish(false);
return true;
} }
private void onError(String msg) { private void onSaveError(String msg) {
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setTitle("Error saving profile") .setTitle("Error saving profile")
.setMessage(msg) .setMessage(msg)
@ -457,27 +464,19 @@ public class EditEntryActivity extends AegisActivity {
.show(); .show();
} }
private void onFieldEdited() { private boolean onSave() {
_edited = true; DatabaseEntry entry;
try {
entry = parseEntry();
} catch (ParseException e) {
onSaveError(e.getMessage());
return false;
}
finish(entry, false);
return true;
} }
private TextWatcher _textListener = new TextWatcher() {
@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 TextWatcher _iconChangeListener = new TextWatcher() { private TextWatcher _iconChangeListener = new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@ -489,36 +488,13 @@ public class EditEntryActivity extends AegisActivity {
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
if (!_hasCustomImage) { if (!_hasCustomIcon) {
TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString(), _iconView); TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString(), _iconView);
_iconView.setImageDrawable(drawable); _iconView.setImageDrawable(drawable);
} }
} }
}; };
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) {
}
}
private int getStringResourceIndex(@ArrayRes int id, String string) { private int getStringResourceIndex(@ArrayRes int id, String string) {
String[] res = getResources().getStringArray(id); String[] res = getResources().getStringArray(id);
for (int i = 0; i < res.length; i++) { for (int i = 0; i < res.length; i++) {
@ -529,7 +505,7 @@ public class EditEntryActivity extends AegisActivity {
return -1; return -1;
} }
public static Bitmap drawableToBitmap(Drawable drawable) { private static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) { if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap(); return ((BitmapDrawable) drawable).getBitmap();
} }
@ -549,4 +525,24 @@ public class EditEntryActivity extends AegisActivity {
return bitmap; return bitmap;
} }
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) {
return null;
}
}
private static class ParseException extends Exception {
public ParseException(String message) {
super(message);
}
}
} }