Switched AppBus for ResultHandler, Controller for Helper. Added the basics for LifecycleControllers and implemented it for BriarActivity and NavDrawerActivity

This commit is contained in:
Ernir Erlingsson
2016-04-07 12:54:23 +02:00
parent 27098db18f
commit a14e981236
32 changed files with 584 additions and 356 deletions

View File

@@ -0,0 +1,11 @@
package org.briarproject.android.controller;
public interface ActivityLifecycleController {
void onActivityCreate();
void onActivityResume();
void onActivityPause();
void onActivityDestroy();
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.android.controller;
public interface BriarController extends ActivityLifecycleController {
void runOnDbThread(final Runnable task);
void startAndBindService();
void unbindService();
boolean encryptionKey();
void signOut(ResultHandler<Void, RuntimeException> eventHandler);
}

View File

@@ -0,0 +1,123 @@
package org.briarproject.android.controller;
import android.app.Activity;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.CallSuper;
import org.briarproject.android.BriarService;
import org.briarproject.android.BriarService.BriarServiceConnection;
import org.briarproject.api.db.DatabaseConfig;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.lifecycle.LifecycleManager;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
public class BriarControllerImp implements BriarController {
private static final Logger LOG =
Logger.getLogger(BriarControllerImp.class.getName());
@Inject
protected BriarServiceConnection serviceConnection;
@Inject
protected DatabaseConfig databaseConfig;
// Fields that are accessed from background threads must be volatile
@Inject
@DatabaseExecutor
protected volatile Executor dbExecutor;
@Inject
protected volatile LifecycleManager lifecycleManager;
@Inject
protected Activity activity;
private boolean bound = false;
@Inject
public BriarControllerImp() {
}
@Override
@CallSuper
public void onActivityCreate() {
if (databaseConfig.getEncryptionKey() != null) startAndBindService();
}
@Override
@CallSuper
public void onActivityResume() {
}
@Override
@CallSuper
public void onActivityPause() {
}
@Override
@CallSuper
public void onActivityDestroy() {
unbindService();
}
public void startAndBindService() {
activity.startService(new Intent(activity, BriarService.class));
bound = activity.bindService(new Intent(activity, BriarService.class),
serviceConnection, 0);
}
@Override
public boolean encryptionKey() {
return databaseConfig.getEncryptionKey() != null;
}
@Override
public void signOut(final ResultHandler<Void, RuntimeException> eventHandler) {
new Thread() {
@Override
public void run() {
try {
// Wait for the service to finish starting up
IBinder binder = serviceConnection.waitForBinder();
BriarService service = ((BriarService.BriarBinder) binder).getService();
service.waitForStartup();
// Shut down the service and wait for it to shut down
LOG.info("Shutting down service");
service.shutdown();
service.waitForShutdown();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for service");
Thread.currentThread().interrupt();
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
eventHandler.onResult(null);
}
});
}
}.start();
}
public void unbindService() {
if (bound) activity.unbindService(serviceConnection);
}
public void runOnDbThread(final Runnable task) {
dbExecutor.execute(new Runnable() {
public void run() {
try {
lifecycleManager.waitForDatabase();
task.run();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for database");
Thread.currentThread().interrupt();
}
}
});
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.android.controller;
public interface ConfigController {
String getEncryptedDatabaseKey();
void clearPrefs();
boolean initialized();
}

View File

@@ -0,0 +1,41 @@
package org.briarproject.android.controller;
import android.content.SharedPreferences;
import org.briarproject.api.db.DatabaseConfig;
import javax.inject.Inject;
public class ConfigControllerImp implements ConfigController {
private final static String PREF_DB_KEY = "key";
@Inject
protected SharedPreferences briarPrefs;
@Inject
protected volatile DatabaseConfig databaseConfig;
@Inject
public ConfigControllerImp() {
}
public String getEncryptedDatabaseKey() {
return briarPrefs.getString(PREF_DB_KEY, null);
}
public void clearPrefs() {
SharedPreferences.Editor editor = briarPrefs.edit();
editor.clear();
editor.apply();
}
@Override
public boolean initialized() {
String hex = getEncryptedDatabaseKey();
if (hex != null && databaseConfig.databaseExists()) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.android.controller;
public class EncryptedKeyNullException extends NullPointerException {
@Override
public String toString() {
return "Encrypted key can't be null";
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.android.controller;
import org.briarproject.api.TransportId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.identity.LocalAuthor;
public interface NavDrawerController extends BriarController {
void setTransportListener(TransportStateListener transportListener);
boolean transportRunning(TransportId transportId);
void storeLocalAuthor(LocalAuthor author,
ResultHandler<Void, DbException> resultHandler);
LocalAuthor removeAuthorHandle(long handle);
}

View File

@@ -0,0 +1,159 @@
package org.briarproject.android.controller;
import android.app.Activity;
import org.briarproject.android.api.ReferenceManager;
import org.briarproject.api.TransportId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.TransportDisabledEvent;
import org.briarproject.api.event.TransportEnabledEvent;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.PluginManager;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
public class NavDrawerControllerImp extends BriarControllerImp
implements NavDrawerController, EventListener {
private static final Logger LOG =
Logger.getLogger(NavDrawerControllerImp.class.getName());
@Inject
protected ReferenceManager referenceManager;
@Inject
protected volatile IdentityManager identityManager;
@Inject
protected PluginManager pluginManager;
@Inject
protected volatile EventBus eventBus;
@Inject
protected Activity activity;
private List<Plugin> transports = new ArrayList<Plugin>();
private TransportStateListener transportStateListener;
@Inject
public NavDrawerControllerImp() {
}
@Override
public void onActivityCreate() {
super.onActivityCreate();
initializeTransports();
}
@Override
public void onActivityResume() {
super.onActivityResume();
eventBus.addListener(this);
}
@Override
public void onActivityPause() {
super.onActivityPause();
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof TransportEnabledEvent) {
TransportId id = ((TransportEnabledEvent) e).getTransportId();
if (LOG.isLoggable(INFO)) {
LOG.info("TransportEnabledEvent: " + id.getString());
}
transportStateUpdate(id, true);
} else if (e instanceof TransportDisabledEvent) {
TransportId id = ((TransportDisabledEvent) e).getTransportId();
if (LOG.isLoggable(INFO)) {
LOG.info("TransportDisabledEvent: " + id.getString());
}
transportStateUpdate(id, false);
}
}
private void transportStateUpdate(final TransportId id, final boolean enabled) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (transportStateListener != null) {
transportStateListener.stateUpdate(id, enabled);
}
}
});
}
private void initializeTransports() {
transports.clear();
transports.add(pluginManager.getPlugin(new TransportId("tor")));
transports.add(pluginManager.getPlugin(new TransportId("bt")));
transports.add(pluginManager.getPlugin(new TransportId("lan")));
}
@Override
public void setTransportListener(TransportStateListener transportListener) {
this.transportStateListener = transportListener;
}
@Override
public boolean transportRunning(TransportId transportId) {
for (Plugin transport : transports) {
if (transport.getId().equals(transportId)) {
return transport.isRunning();
}
}
return false;
}
@Override
public void storeLocalAuthor(final LocalAuthor author,
final ResultHandler<Void, DbException> resultHandler) {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
identityManager.addLocalAuthor(author);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Storing author took " + duration + " ms");
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
resultHandler.onResult(null);
}
});
} catch (final DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
resultHandler.onException(e);
}
});
}
}
});
}
@Override
public LocalAuthor removeAuthorHandle(long handle) {
return referenceManager.removeReference(handle,
LocalAuthor.class);
}
}

View File

@@ -0,0 +1,6 @@
package org.briarproject.android.controller;
public interface PasswordController extends ConfigController {
void validatePassword(String password,
ResultHandler<Boolean, EncryptedKeyNullException> resultHandler);
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.android.controller;
import android.app.Activity;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.util.StringUtils;
import java.util.concurrent.Executor;
import javax.inject.Inject;
public class PasswordControllerImp extends ConfigControllerImp
implements PasswordController {
@Inject
@CryptoExecutor
protected Executor cryptoExecutor;
@Inject
protected CryptoComponent crypto;
@Inject
protected Activity activity;
@Inject
public PasswordControllerImp() {
}
@Override
public void validatePassword(final String password,
final ResultHandler<Boolean, EncryptedKeyNullException> resultHandler) {
final byte[] encrypted = getEncryptedKey();
if (encrypted == null) {
resultHandler.onException(new EncryptedKeyNullException());
}
cryptoExecutor.execute(new Runnable() {
public void run() {
byte[] key = crypto.decryptWithPassword(encrypted, password);
if (key == null) {
onPasswordValidated(false, resultHandler);
} else {
databaseConfig.setEncryptionKey(new SecretKey(key));
onPasswordValidated(true, resultHandler);
}
}
});
}
private void onPasswordValidated(final boolean validated,
final ResultHandler<Boolean, EncryptedKeyNullException> resultHandler) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
resultHandler.onResult(validated);
}
});
}
private byte[] getEncryptedKey() {
String hex = getEncryptedDatabaseKey();
return hex == null ? null : StringUtils.fromHexString(hex);
}
}

View File

@@ -0,0 +1,6 @@
package org.briarproject.android.controller;
public interface ResultHandler<R, E> {
void onResult(R result);
void onException(E exception);
}

View File

@@ -0,0 +1,8 @@
package org.briarproject.android.controller;
public interface SetupController {
float estimatePasswordStrength(String password);
void createIdentity(String nickname, String password,
ResultHandler<Long, RuntimeException> resultHandler);
}

View File

@@ -0,0 +1,118 @@
package org.briarproject.android.controller;
import android.app.Activity;
import android.content.SharedPreferences;
import org.briarproject.android.BaseActivity;
import org.briarproject.android.api.ReferenceManager;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.PasswordStrengthEstimator;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseConfig;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.util.StringUtils;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
public class SetupControllerImp implements SetupController {
private static final Logger LOG =
Logger.getLogger(SetupControllerImp.class.getName());
private final static String PREF_DB_KEY = "key";
@Inject
@CryptoExecutor
protected Executor cryptoExecutor;
@Inject
protected PasswordStrengthEstimator strengthEstimator;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile CryptoComponent crypto;
@Inject
protected volatile DatabaseConfig databaseConfig;
@Inject
protected volatile AuthorFactory authorFactory;
@Inject
protected volatile ReferenceManager referenceManager;
@Inject
protected Activity activity;
@Inject
protected SharedPreferences briarPrefs;
@Inject
public SetupControllerImp() {
}
private String encryptDatabaseKey(SecretKey key, String password) {
long now = System.currentTimeMillis();
byte[] encrypted = crypto.encryptWithPassword(key.getBytes(), password);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Key derivation took " + duration + " ms");
return StringUtils.toHexString(encrypted);
}
private LocalAuthor createLocalAuthor(String nickname) {
long now = System.currentTimeMillis();
KeyPair keyPair = crypto.generateSignatureKeyPair();
byte[] publicKey = keyPair.getPublic().getEncoded();
byte[] privateKey = keyPair.getPrivate().getEncoded();
LocalAuthor localAuthor = authorFactory.createLocalAuthor(nickname,
publicKey, privateKey);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Identity creation took " + duration + " ms");
return localAuthor;
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
@Override
public void createIdentity(final String nickname, final String password,
final ResultHandler<Long, RuntimeException> resultHandler) {
cryptoExecutor.execute(new Runnable() {
public void run() {
SecretKey key = crypto.generateSecretKey();
databaseConfig.setEncryptionKey(key);
String hex = encryptDatabaseKey(key, password);
storeEncryptedDatabaseKey(hex);
final LocalAuthor localAuthor = createLocalAuthor(nickname);
long handle = referenceManager.putReference(localAuthor,
LocalAuthor.class);
onIdentityCreated(handle, resultHandler);
}
});
}
private void onIdentityCreated(final long handle,
final ResultHandler<Long, RuntimeException> resultHandler) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
resultHandler.onResult(handle);
}
});
}
private void storeEncryptedDatabaseKey(final String hex) {
SharedPreferences.Editor editor = briarPrefs.edit();
editor.putString(PREF_DB_KEY, hex);
editor.apply();
}
}

View File

@@ -0,0 +1,7 @@
package org.briarproject.android.controller;
import org.briarproject.api.TransportId;
public interface TransportStateListener {
void stateUpdate(TransportId id, boolean enabled);
}