Merge remote-tracking branch 'origin/custom-images'

This commit is contained in:
Alexander Bakker 2018-06-07 12:27:42 +02:00
commit 04dbb71cd7
17 changed files with 302 additions and 93 deletions

View file

@ -6,6 +6,7 @@ import org.json.JSONObject;
import java.util.List;
import me.impy.aegis.encoding.Base64Exception;
import me.impy.aegis.otp.OtpInfoException;
public class Database {
@ -43,7 +44,7 @@ public class Database {
entry.deserialize(array.getJSONObject(i));
addEntry(entry);
}
} catch (OtpInfoException | JSONException e) {
} catch (Base64Exception | OtpInfoException | JSONException e) {
throw new DatabaseException(e);
}
}

View file

@ -6,6 +6,8 @@ import org.json.JSONObject;
import java.io.Serializable;
import java.util.UUID;
import me.impy.aegis.encoding.Base64;
import me.impy.aegis.encoding.Base64Exception;
import me.impy.aegis.otp.OtpInfo;
import me.impy.aegis.otp.OtpInfoException;
@ -13,8 +15,8 @@ public class DatabaseEntry implements Serializable {
private UUID _uuid;
private String _name = "";
private String _issuer = "";
private String _icon = "";
private OtpInfo _info;
private byte[] _icon;
public DatabaseEntry(OtpInfo info) {
_info = info;
@ -36,6 +38,7 @@ public class DatabaseEntry implements Serializable {
obj.put("name", _name);
obj.put("issuer", _issuer);
obj.put("info", _info.toJson());
obj.put("icon", _icon == null ? JSONObject.NULL : Base64.encode(_icon));
} catch (JSONException e) {
throw new RuntimeException(e);
}
@ -43,7 +46,7 @@ public class DatabaseEntry implements Serializable {
return obj;
}
public void deserialize(JSONObject obj) throws JSONException, OtpInfoException {
public void deserialize(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception {
// if there is no uuid, generate a new one
if (!obj.has("uuid")) {
_uuid = UUID.randomUUID();
@ -53,6 +56,11 @@ public class DatabaseEntry implements Serializable {
_name = obj.getString("name");
_issuer = obj.getString("issuer");
_info = OtpInfo.parseJson(obj.getString("type"), obj.getJSONObject("info"));
String icon = obj.optString("icon", null);
if (icon != null) {
_icon = Base64.decode(icon);
}
}
public UUID getUUID() {
@ -67,7 +75,7 @@ public class DatabaseEntry implements Serializable {
return _issuer;
}
public String getIcon() {
public byte[] getIcon() {
return _icon;
}
@ -83,11 +91,11 @@ public class DatabaseEntry implements Serializable {
_issuer = issuer;
}
public void setIcon(String icon) {
_icon = icon;
}
public void setInfo(OtpInfo info) {
_info = info;
}
public void setIcon(byte[] icon) {
_icon = icon;
}
}

View file

@ -1,5 +1,7 @@
package me.impy.aegis.helpers;
import android.view.View;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
@ -8,7 +10,7 @@ public class TextDrawableHelper {
}
public static TextDrawable generate(String text, String fallback) {
public static TextDrawable generate(String text, String fallback, View view) {
if (text == null || text.isEmpty()) {
if (fallback == null || fallback.isEmpty()) {
return null;
@ -18,6 +20,8 @@ public class TextDrawableHelper {
ColorGenerator generator = ColorGenerator.MATERIAL;
int color = generator.getColor(text);
return TextDrawable.builder().buildRound(text.substring(0, 1).toUpperCase(), color);
return TextDrawable.builder().beginConfig()
.width(view.getWidth())
.height(view.getHeight()).endConfig().buildRect(text.substring(0, 1).toUpperCase(), color);
}
}

View file

@ -1,6 +1,12 @@
package me.impy.aegis.ui;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.ArrayRes;
import android.support.v7.app.ActionBar;
@ -22,7 +28,14 @@ import android.widget.Spinner;
import android.widget.TableRow;
import com.amulyakhare.textdrawable.TextDrawable;
import com.avito.android.krop.KropView;
import com.esafirm.imagepicker.features.ImagePicker;
import com.esafirm.imagepicker.features.ReturnMode;
import com.esafirm.imagepicker.model.Image;
import java.io.ByteArrayOutputStream;
import de.hdodenhof.circleimageview.CircleImageView;
import me.impy.aegis.R;
import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.encoding.Base32;
@ -40,8 +53,9 @@ public class EditEntryActivity extends AegisActivity {
private boolean _isNew = false;
private boolean _edited = false;
private DatabaseEntry _entry;
private ImageView _iconView;
private boolean _hasCustomImage = false;
private CircleImageView _iconView;
private ImageView _saveImageButton;
private EditText _textName;
private EditText _textIssuer;
@ -57,6 +71,8 @@ public class EditEntryActivity extends AegisActivity {
private Spinner _spinnerDigits;
private SpinnerItemSelectedListener _selectedListener = new SpinnerItemSelectedListener();
private KropView _kropView;
private RelativeLayout _advancedSettingsHeader;
private RelativeLayout _advancedSettings;
@ -79,6 +95,8 @@ public class EditEntryActivity extends AegisActivity {
// set up fields
_iconView = findViewById(R.id.profile_drawable);
_kropView = findViewById(R.id.krop_view);
_saveImageButton = findViewById(R.id.iv_saveImage);
_textName = findViewById(R.id.text_name);
_textIssuer = findViewById(R.id.text_issuer);
_textPeriod = findViewById(R.id.text_period);
@ -97,8 +115,15 @@ public class EditEntryActivity extends AegisActivity {
// fill the fields with values if possible
if (_entry != null) {
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName());
_iconView.setImageDrawable(drawable);
if (_entry.getIcon() != null) {
byte[] imageBytes = _entry.getIcon();
Bitmap image = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
_iconView.setImageBitmap(image);
_hasCustomImage = true;
} else {
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName(), _iconView);
_iconView.setImageDrawable(drawable);
}
_textName.setText(_entry.getName());
_textIssuer.setText(_entry.getIssuer());
@ -173,6 +198,24 @@ public class EditEntryActivity extends AegisActivity {
}
});
ImagePicker imagePicker = ImagePicker.create(this)
.returnMode(ReturnMode.ALL) // set whether pick and / or camera action should return immediate result or not.
.folderMode(true) // folder mode (false by default)
.toolbarFolderTitle("Folder") // folder selection title
.toolbarImageTitle("Tap to select") // image selection title
.toolbarArrowColor(Color.BLACK) // Toolbar 'up' arrow color
.single() // single mode
.showCamera(false) // show camera or not (true by default)
.imageDirectory("Camera");
// Open ImagePicker when clicking on the icon
_iconView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imagePicker.start(); // start image picker activity with request code
}
});
_advancedSettingsHeader.setOnClickListener(v -> {
openAdvancedSettings();
});
@ -265,6 +308,10 @@ public class EditEntryActivity extends AegisActivity {
finish(true);
});
break;
case R.id.action_default_image:
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName(), _iconView);
_iconView.setImageDrawable(drawable);
_hasCustomImage = false;
default:
return super.onOptionsItemSelected(item);
}
@ -278,6 +325,10 @@ public class EditEntryActivity extends AegisActivity {
if (_isNew) {
menu.findItem(R.id.action_delete).setVisible(false);
}
if (!_hasCustomImage) {
menu.findItem(R.id.action_default_image).setVisible(false);
}
return true;
}
@ -289,6 +340,29 @@ public class EditEntryActivity extends AegisActivity {
finish();
}
@Override
protected void onActivityResult(int requestCode, final int resultCode, Intent data) {
if (ImagePicker.shouldHandle(requestCode, resultCode, data)) {
// or get a single image only
Image image = ImagePicker.getFirstImageOrNull(data);
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeFile(image.getPath(),bmOptions);
_kropView.setBitmap(bitmap);
_kropView.setVisibility(View.VISIBLE);
_saveImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
_iconView.setImageBitmap(_kropView.getCroppedBitmap());
_kropView.setVisibility(View.GONE);
_hasCustomImage = true;
}
});
}
super.onActivityResult(requestCode, resultCode, data);
}
private boolean onSave() {
if (_textSecret.length() == 0) {
onError("Secret is a required field.");
@ -359,6 +433,15 @@ public class EditEntryActivity extends AegisActivity {
entry.setIssuer(_textIssuer.getText().toString());
entry.setName(_textName.getText().toString());
if (_hasCustomImage) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
drawableToBitmap(_iconView.getDrawable()).compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] bitmapdata = stream.toByteArray();
entry.setIcon(bitmapdata);
} else {
entry.setIcon(null);
}
_entry = entry;
finish(false);
return true;
@ -404,8 +487,10 @@ public class EditEntryActivity extends AegisActivity {
@Override
public void afterTextChanged(Editable s) {
TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString());
_iconView.setImageDrawable(drawable);
if (!_hasCustomImage) {
TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString(), _iconView);
_iconView.setImageDrawable(drawable);
}
}
};
@ -441,4 +526,25 @@ public class EditEntryActivity extends AegisActivity {
}
return -1;
}
public static Bitmap drawableToBitmap(Drawable drawable) {
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;
}
}

View file

@ -1,6 +1,5 @@
package me.impy.aegis.ui;
import android.Manifest;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -57,7 +56,9 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
setWizardMode(true);
showSkipButton(false);
pager.setPagingEnabled(false);
//showPagerIndicator(false);
setGoBackLock(true);
@ -68,19 +69,6 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
homeSliderPage.setBgColor(getResources().getColor(R.color.colorPrimary));
addSlide(AppIntroFragment.newInstance(homeSliderPage));
SliderPage permSliderPage = new SliderPage();
permSliderPage.setTitle("Permissions");
permSliderPage.setDescription("Aegis needs permission to use your camera in order to scan QR codes. " +
"It also needs access to external storage to able to export the database.");
permSliderPage.setImageDrawable(R.drawable.intro_scanner);
permSliderPage.setBgColor(getResources().getColor(R.color.colorAccent));
addSlide(AppIntroFragment.newInstance(permSliderPage));
askForPermissions(new String[]{
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 2);
_authenticationSlide = new CustomAuthenticationSlide();
_authenticationSlide.setBgColor(getResources().getColor(R.color.colorHeaderSuccess));
addSlide(_authenticationSlide);
@ -121,7 +109,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
// skip to the last slide if no encryption will be used
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
// TODO: no magic indices
getPager().setCurrentItem(5);
getPager().setCurrentItem(4);
}
}
}

View file

@ -8,8 +8,10 @@ import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import com.github.paolorotolo.appintro.ISlidePolicy;
@ -23,6 +25,7 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
public static final int CRYPT_TYPE_PASS = 2;
public static final int CRYPT_TYPE_FINGER = 3;
private Spinner _authenticationSpinner;
private RadioGroup _buttonGroup;
private int _bgColor;
@ -38,8 +41,8 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
if (manager != null) {
RadioButton button = view.findViewById(R.id.rb_fingerprint);
TextView text = view.findViewById(R.id.text_rb_fingerprint);
button.setVisibility(View.VISIBLE);
text.setVisibility(View.VISIBLE);
button.setEnabled(false);
text.setEnabled(false);
}
view.findViewById(R.id.main).setBackgroundColor(_bgColor);

View file

@ -1,6 +1,10 @@
package me.impy.aegis.ui.views;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
@ -72,8 +76,14 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_profileIssuer.setText(" - " + entry.getIssuer());
}
TextDrawable drawable = TextDrawableHelper.generate(entry.getIssuer(), entry.getName());
_profileDrawable.setImageDrawable(drawable);
if (entry.getIcon() != null) {
byte[] imageBytes = entry.getIcon();
Bitmap image = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
_profileDrawable.setImageBitmap(image);
} else {
TextDrawable drawable = TextDrawableHelper.generate(entry.getIssuer(), entry.getName(), _profileDrawable);
_profileDrawable.setImageDrawable(drawable);
}
refreshCode();
}