Add an activity to manage database key slots

This commit is contained in:
Alexander Bakker 2018-02-09 17:31:07 +01:00
parent d5f796ca87
commit c24b691a26
24 changed files with 798 additions and 33 deletions

2
.idea/misc.xml generated
View file

@ -24,7 +24,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -49,5 +49,10 @@
</activity>
<activity android:name=".PreferencesActivity">
</activity>
<activity
android:name=".SlotManagerActivity"
android:label="Manage key slots"
android:theme="@style/AppTheme.Default.NoActionBar">
</activity>
</application>
</manifest>

View file

@ -1,6 +1,5 @@
package me.impy.aegis;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
@ -98,12 +97,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
builder.setTitle("Decryption error");
builder.setMessage("Master key integrity check failed for every slot. Make sure you didn't mistype your password.");
builder.setCancelable(false);
builder.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.setPositiveButton(android.R.string.ok, null);
builder.create().show();
}

View file

@ -0,0 +1,79 @@
package me.impy.aegis;
import android.app.Dialog;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.TextView;
import com.mattprecious.swirl.SwirlView;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.slots.FingerprintSlot;
import me.impy.aegis.crypto.slots.Slot;
import me.impy.aegis.helpers.FingerprintHelper;
import me.impy.aegis.helpers.FingerprintUiHelper;
public class FingerprintDialogFragment extends SlotDialogFragment implements FingerprintUiHelper.Callback {
private Cipher _cipher;
private FingerprintUiHelper _helper;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_fingerprint, null);
TextView textFingerprint = view.findViewById(R.id.text_fingerprint);
SwirlView imgFingerprint = view.findViewById(R.id.img_fingerprint);
FingerprintManager manager = FingerprintHelper.getManager(getContext());
try {
KeyStoreHandle handle = new KeyStoreHandle();
SecretKey key = handle.getKey();
_cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
} catch (Exception e) {
throw new RuntimeException(e);
}
return new AlertDialog.Builder(getActivity())
.setTitle("Register a new fingerprint")
.setView(view)
.setNegativeButton(android.R.string.cancel, null)
.create();
}
@Override
public void onResume() {
super.onResume();
if (_helper != null) {
_helper.startListening(new FingerprintManager.CryptoObject(_cipher));
}
}
@Override
public void onPause() {
super.onPause();
if (_helper != null) {
_helper.stopListening();
}
}
@Override
public void onAuthenticated() {
FingerprintSlot slot = new FingerprintSlot();
getListener().onSlotResult(slot, _cipher);
dismiss();
}
@Override
public void onError() {
}
}

View file

@ -26,6 +26,7 @@ import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.crypto.slots.SlotCollection;
import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.db.DatabaseManager;
import me.impy.aegis.helpers.PermissionHelper;
@ -42,6 +43,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
private static final int CODE_DECRYPT = 5;
private static final int CODE_IMPORT = 6;
private static final int CODE_PREFERENCES = 7;
private static final int CODE_SLOTS = 8;
// permission request codes
private static final int CODE_PERM_EXPORT = 0;
@ -183,6 +185,8 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
case CODE_PREFERENCES:
onPreferencesResult(resultCode, data);
break;
case CODE_SLOTS:
onSlotManagerResult(resultCode, data);
}
}
@ -206,6 +210,16 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
}
}
private void onSlotManagerResult(int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
SlotCollection slots = (SlotCollection) data.getSerializableExtra("slots");
_db.getFile().setSlots(slots);
saveDatabase();
}
private void onPreferencesResult(int resultCode, Intent data) {
// refresh the entire key profile list if needed
if (data.getBooleanExtra("needsRefresh", false)) {
@ -219,6 +233,20 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
case PreferencesActivity.ACTION_EXPORT:
onExport();
break;
case PreferencesActivity.ACTION_SLOTS:
MasterKey masterKey;
try {
masterKey = _db.getMasterKey();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to obtain the database key", Toast.LENGTH_SHORT).show();
break;
}
Intent intent = new Intent(this, SlotManagerActivity.class);
intent.putExtra("masterKey", masterKey);
intent.putExtra("slots", _db.getFile().getSlots());
startActivityForResult(intent, CODE_SLOTS);
break;
}
}
@ -554,8 +582,9 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Intent preferencesActivity = new Intent(this, PreferencesActivity.class);
startActivityForResult(preferencesActivity, CODE_PREFERENCES);
Intent intent = new Intent(this, PreferencesActivity.class);
intent.putExtra("encrypted", _db.getFile().isEncrypted());
startActivityForResult(intent, CODE_PREFERENCES);
return true;
case R.id.action_import:
if (PermissionHelper.request(this, CODE_PERM_IMPORT, Manifest.permission.READ_EXTERNAL_STORAGE)) {

View file

@ -0,0 +1,82 @@
package me.impy.aegis;
import android.app.Dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import javax.crypto.Cipher;
import me.impy.aegis.crypto.slots.PasswordSlot;
import me.impy.aegis.crypto.slots.Slot;
import me.impy.aegis.helpers.AuthHelper;
public class PasswordDialogFragment extends SlotDialogFragment {
private Button _buttonOK;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_password, null);
EditText textPassword = view.findViewById(R.id.text_password);
EditText textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
AlertDialog alert = new AlertDialog.Builder(getActivity())
.setTitle("Enter a new password")
.setView(view)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(android.R.string.cancel, null)
.create();
alert.setOnShowListener(dialog -> {
_buttonOK = alert.getButton(AlertDialog.BUTTON_POSITIVE);
_buttonOK.setEnabled(false);
// replace the default listener
_buttonOK.setOnClickListener(v -> {
if (!AuthHelper.arePasswordsEqual(textPassword, textPasswordConfirm)) {
return;
}
AuthHelper.clearPassword(textPasswordConfirm);
char[] password = AuthHelper.getPassword(textPassword, true);
PasswordSlot slot = new PasswordSlot();
DerivationTask task = new DerivationTask(getContext(), key -> {
Cipher cipher;
try {
cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
} catch (Exception e) {
getListener().onException(e);
dialog.cancel();
return;
}
getListener().onSlotResult(slot, cipher);
dialog.dismiss();
});
task.execute(new DerivationTask.Params() {{
Slot = slot;
Password = password;
}});
});
});
TextWatcher watcher = new TextWatcher() {
public void onTextChanged(CharSequence c, int start, int before, int count) {
boolean equal = AuthHelper.arePasswordsEqual(textPassword, textPasswordConfirm);
_buttonOK.setEnabled(equal);
}
public void beforeTextChanged(CharSequence c, int start, int count, int after) { }
public void afterTextChanged(Editable c) { }
};
textPassword.addTextChangedListener(watcher);
textPasswordConfirm.addTextChangedListener(watcher);
return alert;
}
}

View file

@ -9,12 +9,15 @@ import android.widget.Toast;
public class PreferencesActivity extends AegisActivity {
public static final int ACTION_EXPORT = 0;
public static final int ACTION_SLOTS = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction().replace(android.R.id.content, new PreferencesFragment()).commit();
PreferencesFragment fragment = new PreferencesFragment();
fragment.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit();
}
@Override
@ -65,6 +68,17 @@ public class PreferencesActivity extends AegisActivity {
}
});
Preference slotsPreference = findPreference("pref_slots");
slotsPreference.setEnabled(getArguments().getBoolean("encrypted"));
slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
_result.putExtra("action", ACTION_SLOTS);
finish();
return true;
}
});
Preference issuerPreference = findPreference("pref_issuer");
issuerPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override

View file

@ -0,0 +1,70 @@
package me.impy.aegis;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import me.impy.aegis.crypto.slots.Slot;
public class SlotAdapter extends RecyclerView.Adapter<SlotHolder> {
private Listener _listener;
private ArrayList<Slot> _slots;
public SlotAdapter(Listener listener) {
_listener = listener;
_slots = new ArrayList<>();
}
public void addSlot(Slot slot) {
_slots.add(slot);
int position = getItemCount() - 1;
if (position == 0) {
notifyDataSetChanged();
} else {
notifyItemInserted(position);
}
}
public void updateSlot(Slot slot) {
notifyItemChanged(_slots.indexOf(slot));
}
public void removeSlot(Slot slot) {
int position = _slots.indexOf(slot);
_slots.remove(position);
notifyItemRemoved(position);
}
@Override
public SlotHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_slot, parent, false);
return new SlotHolder(view);
}
@Override
public void onBindViewHolder(SlotHolder holder, int position) {
holder.setData(_slots.get(position));
holder.setOnEditClickListener(v -> {
int position1 = holder.getAdapterPosition();
_listener.onEditSlot(_slots.get(position1));
});
holder.setOnDeleteClickListener(v -> {
int position12 = holder.getAdapterPosition();
_listener.onRemoveSlot(_slots.get(position12));
});
}
@Override
public int getItemCount() {
return _slots.size();
}
public interface Listener {
void onEditSlot(Slot slot);
void onRemoveSlot(Slot slot);
}
}

View file

@ -42,7 +42,6 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
if (slot instanceof PasswordSlot) {
char[] password = (char[])params.Obj;
SecretKey key = ((PasswordSlot)slot).deriveKey(password);
CryptoUtils.zero(password);
Cipher cipher = Slot.createCipher(key, Cipher.DECRYPT_MODE);
masterKey = params.Slots.decrypt(slot, cipher);
} else if (slot instanceof FingerprintSlot) {
@ -63,6 +62,10 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
return null;
} catch (Exception e) {
throw new UndeclaredThrowableException(e);
} finally {
if (params.Obj instanceof char[]) {
CryptoUtils.zero((char[]) params.Obj);
}
}
}

View file

@ -0,0 +1,32 @@
package me.impy.aegis;
import android.content.Context;
import android.support.v4.app.DialogFragment;
import javax.crypto.Cipher;
import me.impy.aegis.crypto.slots.Slot;
public class SlotDialogFragment extends DialogFragment {
private Listener _listener;
protected Listener getListener() {
return _listener;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
_listener = (Listener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement SlotDialogFragment.Listener");
}
}
public interface Listener {
void onSlotResult(Slot slot, Cipher cipher);
void onException(Exception e);
}
}

View file

@ -0,0 +1,52 @@
package me.impy.aegis;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import me.impy.aegis.crypto.slots.FingerprintSlot;
import me.impy.aegis.crypto.slots.PasswordSlot;
import me.impy.aegis.crypto.slots.RawSlot;
import me.impy.aegis.crypto.slots.Slot;
public class SlotHolder extends RecyclerView.ViewHolder {
private TextView _slotUsed;
private TextView _slotName;
private ImageView _slotImg;
private LinearLayout _buttonEdit;
private ImageView _buttonDelete;
public SlotHolder(final View view) {
super(view);
_slotUsed = view.findViewById(R.id.text_slot_used);
_slotName = view.findViewById(R.id.text_slot_name);
_slotImg = view.findViewById(R.id.img_slot);
_buttonEdit = view.findViewById(R.id.button_edit);
_buttonDelete = view.findViewById(R.id.button_delete);
}
public void setData(Slot slot) {
if (slot instanceof PasswordSlot) {
_slotName.setText("Password 1");
_slotImg.setImageResource(R.drawable.ic_create_black_24dp);
} else if (slot instanceof FingerprintSlot) {
_slotName.setText("Finger 1");
_slotImg.setImageResource(R.drawable.ic_fingerprint_black_24dp);
} else if (slot instanceof RawSlot) {
_slotName.setText("Raw 1");
_slotImg.setImageResource(R.drawable.ic_vpn_key_black_24dp);
} else {
throw new RuntimeException();
}
}
public void setOnEditClickListener(View.OnClickListener listener) {
_buttonEdit.setOnClickListener(listener);
}
public void setOnDeleteClickListener(View.OnClickListener listener) {
_buttonDelete.setOnClickListener(listener);
}
}

View file

@ -0,0 +1,156 @@
package me.impy.aegis;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import javax.crypto.Cipher;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.crypto.slots.PasswordSlot;
import me.impy.aegis.crypto.slots.Slot;
import me.impy.aegis.crypto.slots.SlotCollection;
import me.impy.aegis.helpers.FingerprintHelper;
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener {
private MasterKey _masterKey;
private SlotCollection _slots;
private SlotAdapter _adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set up the view
setContentView(R.layout.activity_slots);
setSupportActionBar(findViewById(R.id.toolbar));
ActionBar bar = getSupportActionBar();
bar.setHomeAsUpIndicator(R.drawable.ic_close);
bar.setDisplayHomeAsUpEnabled(true);
// only show the fingerprint option if we can get an instance of the fingerprint manager
// TODO: also hide the option if this device's fingerprint has already been registered
if (FingerprintHelper.getManager(this) != null) {
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
FingerprintDialogFragment dialog = new FingerprintDialogFragment();
dialog.show(getSupportFragmentManager(), null);
});
} else {
findViewById(R.id.button_add_fingerprint).setVisibility(View.GONE);
}
findViewById(R.id.button_add_password).setOnClickListener(view -> {
PasswordDialogFragment dialog = new PasswordDialogFragment();
dialog.show(getSupportFragmentManager(), null);
});
// set up the recycler view
_adapter = new SlotAdapter(this);
RecyclerView slotsView = findViewById(R.id.list_slots);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
slotsView.setLayoutManager(layoutManager);
slotsView.setAdapter(_adapter);
slotsView.setNestedScrollingEnabled(false);
// load the slots and masterKey
_masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey");
_slots = (SlotCollection) getIntent().getSerializableExtra("slots");
for (Slot slot : _slots) {
_adapter.addSlot(slot);
}
}
private boolean onSave() {
Intent intent = new Intent();
intent.putExtra("slots", _slots);
setResult(RESULT_OK, intent);
finish();
return true;
}
@Override
protected void setPreferredTheme(boolean nightMode) {
if (nightMode) {
setTheme(R.style.AppTheme_Dark_NoActionBar);
} else {
setTheme(R.style.AppTheme_Default_NoActionBar);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
case R.id.action_save:
return onSave();
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_slots, menu);
return true;
}
@Override
public void onEditSlot(Slot slot) {
EditText textName = new EditText(this);
textName.setHint("Name");
new AlertDialog.Builder(this)
.setTitle("Edit slot name")
.setView(textName)
.setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
String name = textName.getText().toString();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
@Override
public void onRemoveSlot(Slot slot) {
if (slot instanceof PasswordSlot && _slots.findAll(PasswordSlot.class).size() <= 1) {
Toast.makeText(this, "You must have at least one password slot", Toast.LENGTH_SHORT).show();
return;
}
new AlertDialog.Builder(this)
.setTitle("Remove slot")
.setMessage("Are you sure you want to remove this slot?")
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
_slots.remove(slot);
_adapter.removeSlot(slot);
})
.setNegativeButton(android.R.string.no, null)
.show();
}
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
try {
_slots.encrypt(slot, _masterKey, cipher);
} catch (Exception e) {
onException(e);
return;
}
_slots.add(slot);
_adapter.addSlot(slot);
}
@Override
public void onException(Exception e) {
Toast.makeText(this, "An error occurred while trying to add a new slot: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}

View file

@ -167,6 +167,11 @@ public class DatabaseManager {
return _db.getKeys();
}
public MasterKey getMasterKey() throws Exception {
assertState(false, true);
return _key;
}
public DatabaseFile getFile() {
return _file;
}

View file

@ -0,0 +1,7 @@
<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="M11.83,1.73C8.43,1.79 6.23,3.32 6.23,3.32C5.95,3.5 5.88,3.91 6.07,4.19C6.27,4.5 6.66,4.55 6.96,4.34C6.96,4.34 11.27,1.15 17.46,4.38C17.75,4.55 18.14,4.45 18.31,4.15C18.5,3.85 18.37,3.47 18.03,3.28C16.36,2.4 14.78,1.96 13.36,1.8C12.83,1.74 12.32,1.72 11.83,1.73M12.22,4.34C6.26,4.26 3.41,9.05 3.41,9.05C3.22,9.34 3.3,9.72 3.58,9.91C3.87,10.1 4.26,10 4.5,9.68C4.5,9.68 6.92,5.5 12.2,5.59C17.5,5.66 19.82,9.65 19.82,9.65C20,9.94 20.38,10.04 20.68,9.87C21,9.69 21.07,9.31 20.9,9C20.9,9 18.15,4.42 12.22,4.34M11.5,6.82C9.82,6.94 8.21,7.55 7,8.56C4.62,10.53 3.1,14.14 4.77,19C4.88,19.33 5.24,19.5 5.57,19.39C5.89,19.28 6.07,18.92 5.95,18.6V18.6C4.41,14.13 5.78,11.2 7.8,9.5C9.77,7.89 13.25,7.5 15.84,9.1C17.11,9.9 18.1,11.28 18.6,12.64C19.11,14 19.08,15.32 18.67,15.94C18.25,16.59 17.4,16.83 16.65,16.64C15.9,16.45 15.29,15.91 15.26,14.77C15.23,13.06 13.89,12 12.5,11.84C11.16,11.68 9.61,12.4 9.21,14C8.45,16.92 10.36,21.07 14.78,22.45C15.11,22.55 15.46,22.37 15.57,22.04C15.67,21.71 15.5,21.35 15.15,21.25C11.32,20.06 9.87,16.43 10.42,14.29C10.66,13.33 11.5,13 12.38,13.08C13.25,13.18 14,13.7 14,14.79C14.05,16.43 15.12,17.54 16.34,17.85C17.56,18.16 18.97,17.77 19.72,16.62C20.5,15.45 20.37,13.8 19.78,12.21C19.18,10.61 18.07,9.03 16.5,8.04C14.96,7.08 13.19,6.7 11.5,6.82M11.86,9.25V9.26C10.08,9.32 8.3,10.24 7.28,12.18C5.96,14.67 6.56,17.21 7.44,19.04C8.33,20.88 9.54,22.1 9.54,22.1C9.78,22.35 10.17,22.35 10.42,22.11C10.67,21.87 10.67,21.5 10.43,21.23C10.43,21.23 9.36,20.13 8.57,18.5C7.78,16.87 7.3,14.81 8.38,12.77C9.5,10.67 11.5,10.16 13.26,10.67C15.04,11.19 16.53,12.74 16.5,15.03C16.46,15.38 16.71,15.68 17.06,15.7C17.4,15.73 17.7,15.47 17.73,15.06C17.79,12.2 15.87,10.13 13.61,9.47C13.04,9.31 12.45,9.23 11.86,9.25M12.08,14.25C11.73,14.26 11.46,14.55 11.47,14.89C11.47,14.89 11.5,16.37 12.31,17.8C13.15,19.23 14.93,20.59 18.03,20.3C18.37,20.28 18.64,20 18.62,19.64C18.6,19.29 18.3,19.03 17.91,19.06C15.19,19.31 14.04,18.28 13.39,17.17C12.74,16.07 12.72,14.88 12.72,14.88C12.72,14.53 12.44,14.25 12.08,14.25Z" />
</vector>

View file

@ -0,0 +1,7 @@
<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="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</vector>

View file

@ -62,7 +62,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="15dp"
android:text="Touch sensor"
android:text="@string/fingerprint_hint"
android:textColor="?attr/secondaryText"/>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="me.impy.aegis.SlotManagerActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/list_slots"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"/>
<LinearLayout
android:id="@+id/button_add_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="12.5dp"
android:paddingBottom="12.5dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_plus_black_24dp"
android:tint="@color/colorAccent"
android:layout_marginEnd="15dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add password"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
<LinearLayout
android:id="@+id/button_add_fingerprint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="12.5dp"
android:paddingBottom="12.5dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_plus_black_24dp"
android:tint="@color/colorAccent"
android:layout_marginEnd="15dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add fingerprint"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="12.5dp"
android:paddingBottom="12.5dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_info_outline_black_24dp"
android:layout_marginEnd="15dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="The database is only as secure as your weakest key.\n\nWhen a new fingerprint is enrolled, all keys are wiped from the keystore. You should always have at least one password slot to prevent accidental data loss."/>
</LinearLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/button_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingTop="12.5dp"
android:paddingBottom="12.5dp"
android:foreground="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/img_slot"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="15dp"/>
<TextView
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Slot name"
android:id="@+id/text_slot_name"
android:textColor="@color/primary_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView
android:id="@+id/text_slot_used"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:layout_marginTop="12.5dp"
android:layout_marginBottom="12.5dp"/>
<ImageView
android:id="@+id/button_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_delete_black_24dp"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:paddingTop="12.5dp"
android:paddingBottom="12.5dp"
android:foreground="?android:attr/selectableItemBackground"/>
</LinearLayout>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingTop="20dp">
<com.mattprecious.swirl.SwirlView
android:id="@+id/img_fingerprint"
android:layout_width="60dp"
android:layout_height="60dp"/>
<TextView
android:id="@+id/text_fingerprint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:text="@string/fingerprint_hint"
android:textColor="?attr/secondaryText"
android:layout_gravity="center_vertical"/>
</LinearLayout>

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="16dp"
android:paddingTop="16dp">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:text="Name"
android:ems="10"
android:id="@+id/profile_name"
android:layout_weight="0.96"/>
</LinearLayout>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingTop="20dp">
<EditText
android:id="@+id/text_password"
android:hint="Password"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:hint="Confirm password"
android:id="@+id/text_password_confirm"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View file

@ -95,7 +95,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="15dp"
android:text="Touch sensor"/>
android:text="@string/fingerprint_hint"/>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="me.impy.aegis.SlotManagerActivity">
<item
android:id="@+id/action_save"
app:showAsAction="ifRoom"
android:title="@string/save"/>
</menu>

View file

@ -14,6 +14,11 @@
android:title="@string/pref_issuers"
android:summary="@string/pref_issuers_description"/>
<Preference
android:key="pref_slots"
android:title="Key slots"
android:summary="Manage the list of keys that can decrypt the database"/>
<Preference
android:key="pref_export"
android:title="Export"