Fix various issues by moving most global state logic out of MainActivity

* Move global state to a new class that overrides Application
* Make sure all preferences are stored in the same place and follow the same naming convention
This commit is contained in:
Alexander Bakker 2017-12-23 22:33:32 +01:00
parent 862533286b
commit a140ba8506
11 changed files with 152 additions and 96 deletions

View file

@ -2,8 +2,6 @@ package me.impy.aegis;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
@ -15,18 +13,21 @@ import android.widget.TextView;
import me.impy.aegis.crypto.KeyInfo;
public class AddProfileActivity extends AppCompatActivity {
private KeyProfile _keyProfile;
KeyProfile _keyProfile;
private EditText _profileName;
private TextView _textAlgorithm;
private TextView _textIssuer;
private TextView _textPeriod;
private TextView _textOtp;
EditText _profileName;
TextView _textAlgorithm;
TextView _textIssuer;
TextView _textPeriod;
TextView _textOtp;
private AegisApplication _app;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_app = (AegisApplication) getApplication();
setPreferredTheme();
setContentView(R.layout.activity_add_profile);
@ -82,8 +83,7 @@ public class AddProfileActivity extends AppCompatActivity {
}
private void setPreferredTheme() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean("pref_night_mode", false)) {
if (_app.getPreferences().getBoolean("pref_night_mode", false)) {
setTheme(R.style.AppTheme_Dark_TransparentActionBar);
} else {
setTheme(R.style.AppTheme_Default_TransparentActionBar);

View file

@ -0,0 +1,34 @@
package me.impy.aegis;
import android.app.Application;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import me.impy.aegis.db.DatabaseManager;
public class AegisApplication extends Application {
private boolean _running = false;
private DatabaseManager _manager = new DatabaseManager(this);
@Override
public void onCreate() {
super.onCreate();
}
public DatabaseManager getDatabaseManager() {
return _manager;
}
public SharedPreferences getPreferences() {
return PreferenceManager.getDefaultSharedPreferences(this);
}
public boolean isRunning() {
// return false the first time this is called
if (_running) {
return true;
}
_running = true;
return false;
}
}

View file

@ -1,6 +1,5 @@
package me.impy.aegis;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;

View file

@ -1,6 +1,5 @@
package me.impy.aegis;
import android.content.Context;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;

View file

@ -1,9 +1,7 @@
package me.impy.aegis;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -37,9 +35,13 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
private PasswordSlot _passwordSlot;
private Cipher _passwordCipher;
private AegisApplication _app;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_app = (AegisApplication) getApplication();
showSkipButton(false);
//showPagerIndicator(false);
setGoBackLock(true);
@ -189,8 +191,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
setResult(RESULT_OK, result);
// skip the intro from now on
SharedPreferences prefs = this.getSharedPreferences("me.impy.aegis", Context.MODE_PRIVATE);
prefs.edit().putBoolean("passedIntro", true).apply();
_app.getPreferences().edit().putBoolean("pref_intro", true).apply();
finish();
}

View file

@ -13,10 +13,16 @@ import me.impy.aegis.helpers.ItemTouchHelperAdapter;
public class KeyProfileAdapter extends RecyclerView.Adapter<KeyProfileHolder> implements ItemTouchHelperAdapter {
private ArrayList<KeyProfile> _keyProfiles;
private static Listener _listener;
private boolean _showIssuer;
public KeyProfileAdapter(Listener listener) {
public KeyProfileAdapter(Listener listener, boolean showIssuer) {
_keyProfiles = new ArrayList<>();
_listener = listener;
_showIssuer = showIssuer;
}
public void setShowIssuer(boolean showIssuer) {
_showIssuer = showIssuer;
}
public void addKey(KeyProfile profile) {
@ -36,6 +42,11 @@ public class KeyProfileAdapter extends RecyclerView.Adapter<KeyProfileHolder> im
notifyItemRemoved(position);
}
public void clearKeys() {
_keyProfiles.clear();
notifyDataSetChanged();
}
@Override
public void onItemDismiss(int position) {
@ -64,14 +75,14 @@ public class KeyProfileAdapter extends RecyclerView.Adapter<KeyProfileHolder> im
@Override
public void onViewRecycled(KeyProfileHolder holder) {
holder.setData(null);
holder.setData(null, _showIssuer);
super.onViewRecycled(holder);
}
@Override
public void onBindViewHolder(final KeyProfileHolder holder, int position) {
final KeyProfile profile = _keyProfiles.get(position);
holder.setData(profile);
holder.setData(profile, _showIssuer);
holder.startUpdateLoop();
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override

View file

@ -1,9 +1,7 @@
package me.impy.aegis;
import android.animation.ObjectAnimator;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.animation.LinearInterpolator;
@ -21,23 +19,21 @@ public class KeyProfileHolder extends RecyclerView.ViewHolder {
private ImageView _profileDrawable;
private KeyProfile _keyProfile;
private ProgressBar _progressBar;
private View _itemView;
private Handler _uiHandler;
private boolean _running = false;
KeyProfileHolder(final View itemView) {
super(itemView);
_itemView = itemView;
_profileName = itemView.findViewById(R.id.profile_name);
_profileCode = itemView.findViewById(R.id.profile_code);
_profileIssuer = itemView.findViewById(R.id.profile_issuer);
_profileDrawable = itemView.findViewById(R.id.ivTextDrawable);
_progressBar = itemView.findViewById(R.id.progressBar);
KeyProfileHolder(final View view) {
super(view);
_profileName = view.findViewById(R.id.profile_name);
_profileCode = view.findViewById(R.id.profile_code);
_profileIssuer = view.findViewById(R.id.profile_issuer);
_profileDrawable = view.findViewById(R.id.ivTextDrawable);
_progressBar = view.findViewById(R.id.progressBar);
_uiHandler = new Handler();
}
public void setData(KeyProfile profile) {
public void setData(KeyProfile profile, boolean showIssuer) {
if ((_keyProfile = profile) == null) {
_running = false;
return;
@ -46,9 +42,7 @@ public class KeyProfileHolder extends RecyclerView.ViewHolder {
_profileName.setText(profile.getEntry().getName());
_profileCode.setText(profile.getCode());
_profileIssuer.setText("");
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(_itemView.getContext());
if (sharedPreferences.getBoolean("pref_issuer", false)) {
if (showIssuer) {
_profileIssuer.setText(" - " + profile.getEntry().getInfo().getIssuer());
}

View file

@ -6,13 +6,11 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.media.MediaScannerConnection;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
@ -58,6 +56,7 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
private static final int CODE_PERM_CAMERA = 2;
private KeyProfileAdapter _keyProfileAdapter;
private AegisApplication _app;
private DatabaseManager _db;
private boolean _nightMode = false;
@ -66,40 +65,18 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_db = new DatabaseManager(getApplicationContext());
_app = (AegisApplication) getApplication();
_db = _app.getDatabaseManager();
SharedPreferences prefs = this.getSharedPreferences("me.impy.aegis", Context.MODE_PRIVATE);
if (!prefs.getBoolean("passedIntro", false)) {
Intent intro = new Intent(this, IntroActivity.class);
startActivityForResult(intro, CODE_DO_INTRO);
} else {
try {
_db.load();
if (!_db.isDecrypted()) {
Intent intent = new Intent(this, AuthActivity.class);
intent.putExtra("slots", _db.getFile().getSlots());
startActivityForResult(intent, CODE_DECRYPT);
}
} catch (FileNotFoundException e) {
// start the intro if the db file was not found
Toast.makeText(this, "Database file not found, starting over...", Toast.LENGTH_SHORT).show();
Intent intro = new Intent(this, IntroActivity.class);
startActivityForResult(intro, CODE_DO_INTRO);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to deserialize the database", Toast.LENGTH_LONG).show();
throw new UndeclaredThrowableException(e);
}
}
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean("pref_night_mode", false)) {
// set the theme
if (_app.getPreferences().getBoolean("pref_night_mode", false)) {
_nightMode = true;
setTheme(R.style.AppTheme_Dark_NoActionBar);
} else {
setPreferredTheme();
}
// set up the main view
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
@ -108,25 +85,49 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
initializeAppShortcuts();
doShortcutActions();
// set up the floating action button
FloatingActionButton fab = findViewById(R.id.fab);
fab.setEnabled(true);
fab.setOnClickListener(view -> {
onGetKeyInfo();
});
fab.setOnClickListener(view -> onGetKeyInfo());
// set up the recycler view for the key profiles
_keyProfileAdapter = new KeyProfileAdapter(this, _app.getPreferences().getBoolean("pref_issuer", false));
RecyclerView rvKeyProfiles = findViewById(R.id.rvKeyProfiles);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
rvKeyProfiles.setLayoutManager(mLayoutManager);
_keyProfileAdapter = new KeyProfileAdapter(this);
if (_db.isDecrypted()) {
loadKeyProfiles();
}
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(_keyProfileAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(rvKeyProfiles);
rvKeyProfiles.setAdapter(_keyProfileAdapter);
if (!_app.isRunning() && !_db.isUnlocked()) {
if (!_app.getPreferences().getBoolean("pref_intro", false)) {
Intent intro = new Intent(this, IntroActivity.class);
startActivityForResult(intro, CODE_DO_INTRO);
} else {
try {
_db.load();
if (!_db.isUnlocked()) {
Intent intent = new Intent(this, AuthActivity.class);
intent.putExtra("slots", _db.getFile().getSlots());
startActivityForResult(intent, CODE_DECRYPT);
}
} catch (FileNotFoundException e) {
// start the intro if the db file was not found
Toast.makeText(this, "Database file not found, starting over...", Toast.LENGTH_SHORT).show();
Intent intro = new Intent(this, IntroActivity.class);
startActivityForResult(intro, CODE_DO_INTRO);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to deserialize the database", Toast.LENGTH_LONG).show();
throw new UndeclaredThrowableException(e);
}
}
}
if (_db.isUnlocked()) {
loadKeyProfiles();
}
}
@Override
@ -176,6 +177,8 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
private void onPreferencesResult(int resultCode, Intent data) {
// refresh the entire key profile list if needed
if (data.getBooleanExtra("needsRefresh", false)) {
boolean showIssuer = _app.getPreferences().getBoolean("pref_issuer", false);
_keyProfileAdapter.setShowIssuer(showIssuer);
_keyProfileAdapter.notifyDataSetChanged();
}
@ -344,8 +347,8 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
MasterKey key = (MasterKey) data.getSerializableExtra("key");
try {
_db.load();
if (!_db.isDecrypted()) {
_db.setMasterKey(key);
if (!_db.isUnlocked()) {
_db.unlock(key);
}
} catch (Exception e) {
e.printStackTrace();
@ -360,7 +363,7 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
private void onDecryptResult(int resultCode, Intent data) {
MasterKey key = (MasterKey) data.getSerializableExtra("key");
try {
_db.setMasterKey(key);
_db.unlock(key);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to decrypt the database", Toast.LENGTH_LONG).show();
@ -375,7 +378,7 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
private void doShortcutActions() {
Intent intent = getIntent();
String mode = intent.getStringExtra("Action");
if (mode == null || !_db.isDecrypted()) {
if (mode == null || !_db.isUnlocked()) {
return;
}
@ -475,8 +478,16 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
}
return true;
case R.id.action_lock:
// TODO: properly close the database
recreate();
_keyProfileAdapter.clearKeys();
try {
_db.lock();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to lock the database", Toast.LENGTH_LONG).show();
}
Intent intent = new Intent(this, AuthActivity.class);
intent.putExtra("slots", _db.getFile().getSlots());
startActivityForResult(intent, CODE_DECRYPT);
return true;
default:
return super.onOptionsItemSelected(item);
@ -514,8 +525,7 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
private void setPreferredTheme() {
boolean restart = false;
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean("pref_night_mode", false)) {
if (_app.getPreferences().getBoolean("pref_night_mode", false)) {
if (!_nightMode) {
setTheme(R.style.AppTheme_Dark_NoActionBar);
restart = true;
@ -555,7 +565,7 @@ public class MainActivity extends AppCompatActivity implements KeyProfileAdapter
private void updateLockIcon() {
// hide the lock icon if the database is not encrypted
if (_menu != null && _db.isDecrypted()) {
if (_menu != null && _db.isUnlocked()) {
MenuItem item = _menu.findItem(R.id.action_lock);
item.setVisible(_db.getFile().isEncrypted());
}

View file

@ -1,23 +1,23 @@
package me.impy.aegis;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
public class PreferencesActivity extends AppCompatActivity {
public static final int ACTION_EXPORT = 0;
private AegisApplication _app;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_app = (AegisApplication) getApplication();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
if (preferences.getBoolean("pref_night_mode", false)) {
if (_app.getPreferences().getBoolean("pref_night_mode", false)) {
setTheme(R.style.AppTheme_Dark);
} else {
setTheme(R.style.AppTheme_Default);

View file

@ -57,7 +57,14 @@ public class DatabaseManager {
}
}
public void setMasterKey(MasterKey key) throws Exception {
public void lock() throws Exception {
assertUnlocked();
// TODO: properly clear everything
_key = null;
_db = null;
}
public void unlock(MasterKey key) throws Exception {
assertLoaded();
byte[] encrypted = _file.getContent();
CryptParameters params = _file.getCryptParameters();
@ -83,7 +90,7 @@ public class DatabaseManager {
}
public void save() throws Exception {
assertDecrypted();
assertUnlocked();
byte[] dbBytes = _db.serialize();
if (!_file.isEncrypted()) {
_file.setContent(dbBytes);
@ -96,7 +103,7 @@ public class DatabaseManager {
}
public String export(boolean encrypt) throws Exception {
assertDecrypted();
assertUnlocked();
byte[] bytes = _db.serialize();
encrypt = encrypt && getFile().isEncrypted();
if (encrypt) {
@ -129,22 +136,22 @@ public class DatabaseManager {
}
public void addKey(DatabaseEntry entry) throws Exception {
assertDecrypted();
assertUnlocked();
_db.addKey(entry);
}
public void removeKey(DatabaseEntry entry) throws Exception {
assertDecrypted();
assertUnlocked();
_db.removeKey(entry);
}
public void swapKeys(DatabaseEntry entry1, DatabaseEntry entry2) throws Exception {
assertDecrypted();
assertUnlocked();
_db.swapKeys(entry1, entry2);
}
public List<DatabaseEntry> getKeys() throws Exception {
assertDecrypted();
assertUnlocked();
return _db.getKeys();
}
@ -156,7 +163,7 @@ public class DatabaseManager {
return _file != null;
}
public boolean isDecrypted() {
public boolean isUnlocked() {
return _db != null;
}
@ -166,10 +173,10 @@ public class DatabaseManager {
}
}
private void assertDecrypted() throws Exception {
private void assertUnlocked() throws Exception {
assertLoaded();
if (!isDecrypted()) {
throw new Exception("database file has not been decrypted yet");
if (!isUnlocked()) {
throw new Exception("database file has not been unlocked yet");
}
}
}