Replace the FAB with a FAB menu and allow manually entering OTP details

This commit is contained in:
Alexander Bakker 2017-12-30 00:26:16 +01:00
parent 3a396fe3f6
commit 44139de212
11 changed files with 152 additions and 51 deletions

View file

@ -40,5 +40,6 @@ dependencies {
compile 'com.mattprecious.swirl:swirl:1.0.0' compile 'com.mattprecious.swirl:swirl:1.0.0'
compile 'com.madgag.spongycastle:core:1.56.0.0' compile 'com.madgag.spongycastle:core:1.56.0.0'
compile 'com.github.apl-devs:appintro:v4.2.2' compile 'com.github.apl-devs:appintro:v4.2.2'
compile 'com.getbase:floatingactionbutton:1.10.1'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
} }

View file

@ -40,52 +40,64 @@ public class EditProfileActivity extends AegisActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_profile); setContentView(R.layout.activity_edit_profile);
_profile = (KeyProfile) getIntent().getSerializableExtra("KeyProfile");
ActionBar bar = getSupportActionBar(); ActionBar bar = getSupportActionBar();
bar.setHomeAsUpIndicator(R.drawable.ic_close); bar.setHomeAsUpIndicator(R.drawable.ic_close);
bar.setDisplayHomeAsUpEnabled(true); bar.setDisplayHomeAsUpEnabled(true);
// if the intent doesn't contain a KeyProfile, create a new oneZ
_profile = (KeyProfile) getIntent().getSerializableExtra("KeyProfile");
if (_profile == null) {
_profile = new KeyProfile();
setTitle("Add profile");
}
_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);
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);
}
private void updateFields() {
DatabaseEntry entry = _profile.getEntry();
ImageView imageView = findViewById(R.id.profile_drawable); ImageView imageView = findViewById(R.id.profile_drawable);
imageView.setImageDrawable(_profile.getDrawable()); imageView.setImageDrawable(_profile.getDrawable());
DatabaseEntry entry = _profile.getEntry();
_textName = findViewById(R.id.text_name);
_textName.setText(entry.getName()); _textName.setText(entry.getName());
_textName.addTextChangedListener(watcher);
_textIssuer = findViewById(R.id.text_issuer);
_textIssuer.setText(entry.getInfo().getIssuer()); _textIssuer.setText(entry.getInfo().getIssuer());
_textIssuer.addTextChangedListener(watcher);
_textPeriod = findViewById(R.id.text_period);
_textPeriod.setText(Integer.toString(entry.getInfo().getPeriod())); _textPeriod.setText(Integer.toString(entry.getInfo().getPeriod()));
_textPeriod.addTextChangedListener(watcher);
_textSecret = findViewById(R.id.text_secret); byte[] secretBytes = entry.getInfo().getSecret();
_textSecret.setText(Base32.encodeOriginal(entry.getInfo().getSecret())); if (secretBytes != null) {
_textSecret.addTextChangedListener(watcher); _textSecret.setText(Base32.encodeOriginal(secretBytes));
}
String type = entry.getInfo().getType(); String type = entry.getInfo().getType();
_spinnerType = findViewById(R.id.spinner_type);
SpinnerHelper.fillSpinner(this, _spinnerType, R.array.otp_types_array);
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type), false); _spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type), false);
_spinnerType.setOnTouchListener(_selectedListener);
_spinnerType.setOnItemSelectedListener(_selectedListener);
String algo = entry.getInfo().getAlgorithm(false); String algo = entry.getInfo().getAlgorithm(false);
_spinnerAlgo = findViewById(R.id.spinner_algo);
SpinnerHelper.fillSpinner(this, _spinnerAlgo, R.array.otp_algo_array);
_spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false); _spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false);
_spinnerAlgo.setOnTouchListener(_selectedListener);
_spinnerAlgo.setOnItemSelectedListener(_selectedListener);
String digits = Integer.toString(entry.getInfo().getDigits()); String digits = Integer.toString(entry.getInfo().getDigits());
_spinnerDigits = findViewById(R.id.spinner_digits);
SpinnerHelper.fillSpinner(this, _spinnerDigits, R.array.otp_digits_array);
_spinnerDigits.setSelection(getStringResourceIndex(R.array.otp_digits_array, digits), false); _spinnerDigits.setSelection(getStringResourceIndex(R.array.otp_digits_array, digits), false);
_spinnerDigits.setOnTouchListener(_selectedListener);
_spinnerDigits.setOnItemSelectedListener(_selectedListener);
} }
@Override @Override
@ -185,7 +197,7 @@ public class EditProfileActivity extends AegisActivity {
_edited = true; _edited = true;
} }
private TextWatcher watcher = new TextWatcher() { private TextWatcher _textListener = 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) {
onFieldEdited(); onFieldEdited();

View file

@ -13,6 +13,10 @@ public class KeyProfile implements Serializable {
private String _code; private String _code;
private DatabaseEntry _entry; private DatabaseEntry _entry;
public KeyProfile() {
this(new DatabaseEntry());
}
public KeyProfile(DatabaseEntry entry) { public KeyProfile(DatabaseEntry entry) {
_entry = entry; _entry = entry;
} }
@ -35,7 +39,7 @@ public class KeyProfile implements Serializable {
public TextDrawable getDrawable() { public TextDrawable getDrawable() {
String name = _entry.getName(); String name = _entry.getName();
if (name == null) { if (name == null || name.length() <= 1) {
return null; return null;
} }

View file

@ -8,7 +8,6 @@ import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
import android.support.design.widget.BottomSheetDialog; import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
@ -18,6 +17,9 @@ import android.view.View;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Toast; import android.widget.Toast;
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.UndeclaredThrowableException;
@ -32,13 +34,14 @@ import me.impy.aegis.util.ByteInputStream;
public class MainActivity extends AegisActivity implements KeyProfileView.Listener { public class MainActivity extends AegisActivity implements KeyProfileView.Listener {
// activity request codes // activity request codes
private static final int CODE_GET_KEYINFO = 0; private static final int CODE_SCAN_KEYINFO = 0;
private static final int CODE_ADD_KEYINFO = 1; private static final int CODE_ADD_KEYINFO = 1;
private static final int CODE_EDIT_KEYINFO = 2; private static final int CODE_EDIT_KEYINFO = 2;
private static final int CODE_DO_INTRO = 3; private static final int CODE_ENTER_KEYINFO = 3;
private static final int CODE_DECRYPT = 4; private static final int CODE_DO_INTRO = 4;
private static final int CODE_IMPORT = 5; private static final int CODE_DECRYPT = 5;
private static final int CODE_PREFERENCES = 6; private static final int CODE_IMPORT = 6;
private static final int CODE_PREFERENCES = 7;
// permission request codes // permission request codes
private static final int CODE_PERM_EXPORT = 0; private static final int CODE_PERM_EXPORT = 0;
@ -60,8 +63,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
// set up the main view // set up the main view
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(findViewById(R.id.toolbar));
setSupportActionBar(toolbar);
// set up the key profile view // set up the key profile view
_keyProfileView = (KeyProfileView) getSupportFragmentManager().findFragmentById(R.id.key_profiles); _keyProfileView = (KeyProfileView) getSupportFragmentManager().findFragmentById(R.id.key_profiles);
@ -69,9 +71,15 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
_keyProfileView.setShowIssuer(_app.getPreferences().getBoolean("pref_issuer", false)); _keyProfileView.setShowIssuer(_app.getPreferences().getBoolean("pref_issuer", false));
// set up the floating action button // set up the floating action button
FloatingActionButton fab = findViewById(R.id.fab); FloatingActionsMenu fabMenu = findViewById(R.id.fab);
fab.setEnabled(true); findViewById(R.id.fab_enter).setOnClickListener(view -> {
fab.setOnClickListener(view -> onGetKeyInfo()); fabMenu.collapse();
onEnterKeyInfo();
});
findViewById(R.id.fab_scan).setOnClickListener(view -> {
fabMenu.collapse();
onScanKeyInfo();
});
// skip this part if this is the not initial startup and the database has been unlocked // skip this part if this is the not initial startup and the database has been unlocked
if (!_app.isRunning() && _db.isLocked()) { if (!_app.isRunning() && _db.isLocked()) {
@ -133,8 +141,8 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
} }
switch (requestCode) { switch (requestCode) {
case CODE_GET_KEYINFO: case CODE_SCAN_KEYINFO:
onGetKeyInfoResult(resultCode, data); onScanKeyInfoResult(resultCode, data);
break; break;
case CODE_ADD_KEYINFO: case CODE_ADD_KEYINFO:
onAddKeyInfoResult(resultCode, data); onAddKeyInfoResult(resultCode, data);
@ -142,6 +150,9 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
case CODE_EDIT_KEYINFO: case CODE_EDIT_KEYINFO:
onEditKeyInfoResult(resultCode, data); onEditKeyInfoResult(resultCode, data);
break; break;
case CODE_ENTER_KEYINFO:
onEnterKeyInfoResult(resultCode, data);
break;
case CODE_DO_INTRO: case CODE_DO_INTRO:
onDoIntroResult(resultCode, data); onDoIntroResult(resultCode, data);
break; break;
@ -172,7 +183,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
onImport(); onImport();
break; break;
case CODE_PERM_CAMERA: case CODE_PERM_CAMERA:
onGetKeyInfo(); onScanKeyInfo();
break; break;
} }
} }
@ -297,7 +308,12 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
saveDatabase(); saveDatabase();
} }
private void onGetKeyInfo() { private void onEnterKeyInfo() {
Intent intent = new Intent(this, EditProfileActivity.class);
startActivityForResult(intent, CODE_ENTER_KEYINFO);
}
private void onScanKeyInfo() {
if (!PermissionHelper.request(this, CODE_PERM_CAMERA, Manifest.permission.CAMERA)) { if (!PermissionHelper.request(this, CODE_PERM_CAMERA, Manifest.permission.CAMERA)) {
return; return;
} }
@ -305,7 +321,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
startScanActivity(); startScanActivity();
} }
private void onGetKeyInfoResult(int resultCode, Intent data) { private void onScanKeyInfoResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
KeyProfile keyProfile = (KeyProfile)data.getSerializableExtra("KeyProfile"); KeyProfile keyProfile = (KeyProfile)data.getSerializableExtra("KeyProfile");
Intent intent = new Intent(this, AddProfileActivity.class); Intent intent = new Intent(this, AddProfileActivity.class);
@ -339,6 +355,21 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
} }
} }
private void onEnterKeyInfoResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
KeyProfile profile = (KeyProfile) data.getSerializableExtra("KeyProfile");
try {
addKey(profile);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to add an entry", Toast.LENGTH_SHORT).show();
return;
}
_keyProfileView.addKey(profile);
saveDatabase();
}
}
private void addKey(KeyProfile profile) { private void addKey(KeyProfile profile) {
profile.refreshCode(); profile.refreshCode();
@ -395,7 +426,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
private void startScanActivity() { private void startScanActivity() {
Intent scannerActivity = new Intent(getApplicationContext(), ScannerActivity.class); Intent scannerActivity = new Intent(getApplicationContext(), ScannerActivity.class);
startActivityForResult(scannerActivity, CODE_GET_KEYINFO); startActivityForResult(scannerActivity, CODE_SCAN_KEYINFO);
} }
private boolean doShortcutActions() { private boolean doShortcutActions() {

View file

@ -7,11 +7,11 @@ import java.io.Serializable;
import me.impy.aegis.encoding.Base32; import me.impy.aegis.encoding.Base32;
public class KeyInfo implements Serializable { public class KeyInfo implements Serializable {
private String _type; private String _type = "totp";
private byte[] _secret; private byte[] _secret;
private String _accountName; private String _accountName = "";
private String _issuer; private String _issuer = "";
private long _counter; private long _counter = 0;
private String _algorithm = "SHA1"; private String _algorithm = "SHA1";
private int _digits = 6; private int _digits = 6;
private int _period = 30; private int _period = 30;

View file

@ -13,6 +13,10 @@ public class DatabaseEntry implements Serializable {
private String _icon = ""; private String _icon = "";
private KeyInfo _info; private KeyInfo _info;
public DatabaseEntry() {
this(new KeyInfo());
}
public DatabaseEntry(KeyInfo info) { public DatabaseEntry(KeyInfo info) {
_info = info; _info = info;
} }

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#B2000000"/>
<padding
android:left="16dp"
android:top="4dp"
android:right="16dp"
android:bottom="4dp"/>
<corners
android:radius="2dp"/>
</shape>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z" />
</vector>

View file

@ -29,13 +29,38 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior"/> app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<!-- note: the fab should always be the last element to be sure it's displayed on top --> <!-- note: the fab should always be the last element to be sure it's displayed on top -->
<android.support.design.widget.FloatingActionButton <com.getbase.floatingactionbutton.FloatingActionsMenu
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin" android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_add_black_24dp" android:src="@drawable/ic_add_black_24dp"
android:tint="@color/background"/> app:fab_addButtonColorNormal="@color/colorAccent"
app:fab_addButtonColorPressed="@color/colorAccent"
app:fab_labelStyle="@style/fab_label_style"
app:fab_labelsPosition="left">
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fab_title="Scan QR code"
app:fab_size="mini"
app:fab_colorNormal="@color/background"
app:fab_colorPressed="@color/background"
app:fab_icon="@drawable/ic_qrcode_scan"/>
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_enter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fab_title="Enter manually"
app:fab_size="mini"
app:fab_colorNormal="@color/background"
app:fab_colorPressed="@color/background"
app:fab_icon="@drawable/ic_create_black_24dp"/>
</com.getbase.floatingactionbutton.FloatingActionsMenu>
</android.support.design.widget.CoordinatorLayout> </android.support.design.widget.CoordinatorLayout>

View file

@ -5,6 +5,7 @@
<color name="colorHeaderSuccess">#12b600</color> <color name="colorHeaderSuccess">#12b600</color>
<color name="colorPrimaryLight">#B3E5FC</color> <color name="colorPrimaryLight">#B3E5FC</color>
<color name="colorAccent">#FF5252</color> <color name="colorAccent">#FF5252</color>
<color name="colorAccentPressed">#FF5252</color>
<color name="primary_text">#212121</color> <color name="primary_text">#212121</color>
<color name="secondary_text">#434343</color> <color name="secondary_text">#434343</color>
<color name="extra_info_text">#8e8e8e</color> <color name="extra_info_text">#8e8e8e</color>

View file

@ -94,5 +94,9 @@
<item name="windowActionBarOverlay">true</item> <item name="windowActionBarOverlay">true</item>
</style> </style>
<style name="fab_label_style">
<item name="android:background">@drawable/fab_label_background</item>
<item name="android:textColor">#FFFFFF</item>
</style>
</resources> </resources>