mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
115 Commits
1387-persi
...
beta-0.16.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f8e96a654 | ||
|
|
84e040605b | ||
|
|
b0aa1517e5 | ||
|
|
2ac9f567dc | ||
|
|
792cfd7d6f | ||
|
|
2112d4fa7d | ||
|
|
31ca04e070 | ||
|
|
9693a5cb93 | ||
|
|
82266345ae | ||
|
|
0942fe6053 | ||
|
|
4a1f58705d | ||
|
|
cfe0d9a656 | ||
|
|
3cf61e7b3d | ||
|
|
7bb7f8ad5b | ||
|
|
fc50bb1c6c | ||
|
|
19be4d6edf | ||
|
|
b2e4de91a4 | ||
|
|
9b184fe1d9 | ||
|
|
f4ddc01641 | ||
|
|
08b63201d9 | ||
|
|
1c41181f1c | ||
|
|
246b330b36 | ||
|
|
fd3e74cefc | ||
|
|
ef12191ec8 | ||
|
|
a9fc310762 | ||
|
|
0a70c2d44d | ||
|
|
af1fc6f095 | ||
|
|
21956f2627 | ||
|
|
55db6e524a | ||
|
|
dac3de24e7 | ||
|
|
f93f41893e | ||
|
|
7dacb43e01 | ||
|
|
6a962bad24 | ||
|
|
489c0154e9 | ||
|
|
85dc99da72 | ||
|
|
ec808fd9f7 | ||
|
|
4c661cd4bb | ||
|
|
6324fb72a5 | ||
|
|
d3aebc4aba | ||
|
|
65c0e110c5 | ||
|
|
67aeb40d34 | ||
|
|
8280b2e3b8 | ||
|
|
4e0b9145c1 | ||
|
|
0ad4f2f39b | ||
|
|
812522a900 | ||
|
|
98db9da4bc | ||
|
|
eda3c964aa | ||
|
|
68df606146 | ||
|
|
52bd699d2d | ||
|
|
abb8db10db | ||
|
|
30edb90426 | ||
|
|
ffc94b2812 | ||
|
|
35a7bb4576 | ||
|
|
2d87e34aa2 | ||
|
|
088564f22f | ||
|
|
8c8c1158f4 | ||
|
|
8faa456eb2 | ||
|
|
4c61158326 | ||
|
|
6792abc00a | ||
|
|
63442aea1d | ||
|
|
a58443eaa8 | ||
|
|
14a9614c35 | ||
|
|
f1011b97b3 | ||
|
|
1935b1e09a | ||
|
|
ac9df9d5d8 | ||
|
|
30a800a4d0 | ||
|
|
69537b67a2 | ||
|
|
92982f98a8 | ||
|
|
ea5fa72224 | ||
|
|
5a1651d483 | ||
|
|
fcbf6dfb7f | ||
|
|
7aebf92a6f | ||
|
|
1b9f8d4f0b | ||
|
|
93db4eb986 | ||
|
|
347c2f22c1 | ||
|
|
a8ea191ffb | ||
|
|
2a4c22757b | ||
|
|
28ebbbc7d1 | ||
|
|
5e7d08f05d | ||
|
|
ea005748dc | ||
|
|
b021bfab5e | ||
|
|
29cd105a1d | ||
|
|
be2e68e96c | ||
|
|
9dd3f81bb7 | ||
|
|
5d918591d4 | ||
|
|
f1c027fa4d | ||
|
|
d2d3ccf68d | ||
|
|
f4efed54d5 | ||
|
|
459538e40c | ||
|
|
183f501761 | ||
|
|
65ee5f539b | ||
|
|
604339326c | ||
|
|
0acec1343f | ||
|
|
0434756bbd | ||
|
|
e233433140 | ||
|
|
c63f285f53 | ||
|
|
0800188718 | ||
|
|
6188e48beb | ||
|
|
5726e29b56 | ||
|
|
5d70399de0 | ||
|
|
73202dde5e | ||
|
|
a98ac8233c | ||
|
|
bee3e244fc | ||
|
|
da25999a15 | ||
|
|
62049df342 | ||
|
|
024e5aa90f | ||
|
|
6d791481d5 | ||
|
|
0a807d0893 | ||
|
|
23596bbdd4 | ||
|
|
fe79954138 | ||
|
|
9902c023ca | ||
|
|
e8baee6734 | ||
|
|
a8dc029e56 | ||
|
|
74e3fee7aa | ||
|
|
05aac696b7 |
@@ -12,8 +12,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 1612
|
versionCode 1619
|
||||||
versionName "0.16.12"
|
versionName "0.16.19"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,16 +54,16 @@ dependencyVerification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext.torBinaryDir = 'src/main/res/raw'
|
ext.torBinaryDir = 'src/main/res/raw'
|
||||||
ext.torVersion = '0.2.9.12'
|
ext.torVersion = '0.2.9.14'
|
||||||
ext.geoipVersion = '2017-09-06'
|
ext.geoipVersion = '2017-11-06'
|
||||||
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
||||||
|
|
||||||
def torBinaries = [
|
def torBinaries = [
|
||||||
"tor_arm" : '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688',
|
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
|
||||||
"tor_arm_pie": '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283',
|
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
|
||||||
"tor_x86" : '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab',
|
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
|
||||||
"tor_x86_pie": '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288',
|
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
|
||||||
"geoip" : 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
|
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
||||||
]
|
]
|
||||||
|
|
||||||
def downloadBinary(name) {
|
def downloadBinary(name) {
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
|||||||
import org.briarproject.bramble.api.reporting.DevReporter;
|
import org.briarproject.bramble.api.reporting.DevReporter;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
|
import org.briarproject.bramble.api.system.Scheduler;
|
||||||
|
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
@@ -33,16 +35,18 @@ public class AndroidPluginModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
||||||
|
@Scheduler ScheduledExecutorService scheduler,
|
||||||
AndroidExecutor androidExecutor, SecureRandom random,
|
AndroidExecutor androidExecutor, SecureRandom random,
|
||||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||||
Application app, LocationUtils locationUtils, DevReporter reporter,
|
Application app, LocationUtils locationUtils, DevReporter reporter,
|
||||||
EventBus eventBus) {
|
EventBus eventBus) {
|
||||||
Context appContext = app.getApplicationContext();
|
Context appContext = app.getApplicationContext();
|
||||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
DuplexPluginFactory bluetooth =
|
||||||
androidExecutor, appContext, random, eventBus, backoffFactory);
|
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
|
appContext, random, eventBus, backoffFactory);
|
||||||
locationUtils, reporter, eventBus, torSocketFactory,
|
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||||
backoffFactory);
|
appContext, locationUtils, reporter, eventBus,
|
||||||
|
torSocketFactory, backoffFactory);
|
||||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||||
backoffFactory, appContext);
|
backoffFactory, appContext);
|
||||||
Collection<DuplexPluginFactory> duplex =
|
Collection<DuplexPluginFactory> duplex =
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package org.briarproject.bramble.plugin.bluetooth;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothServerSocket;
|
||||||
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginException;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
||||||
|
|
||||||
|
private final AndroidExecutor androidExecutor;
|
||||||
|
private final Context appContext;
|
||||||
|
|
||||||
|
private volatile boolean wasEnabledByUs = false;
|
||||||
|
private volatile BluetoothStateReceiver receiver = null;
|
||||||
|
|
||||||
|
// Non-null if the plugin started successfully
|
||||||
|
private volatile BluetoothAdapter adapter = null;
|
||||||
|
|
||||||
|
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||||
|
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||||
|
DuplexPluginCallback callback, int maxLatency) {
|
||||||
|
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||||
|
this.androidExecutor = androidExecutor;
|
||||||
|
this.appContext = appContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() throws PluginException {
|
||||||
|
super.start();
|
||||||
|
// Listen for changes to the Bluetooth state
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_STATE_CHANGED);
|
||||||
|
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||||
|
receiver = new BluetoothStateReceiver();
|
||||||
|
appContext.registerReceiver(receiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
super.stop();
|
||||||
|
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void initialiseAdapter() throws IOException {
|
||||||
|
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
||||||
|
// with a message queue, so submit it to the AndroidExecutor
|
||||||
|
try {
|
||||||
|
adapter = androidExecutor.runOnBackgroundThread(
|
||||||
|
BluetoothAdapter::getDefaultAdapter).get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
if (adapter == null)
|
||||||
|
throw new IOException("Bluetooth is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isAdapterEnabled() {
|
||||||
|
return adapter != null && adapter.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void enableAdapter() {
|
||||||
|
if (adapter != null && !adapter.isEnabled()) {
|
||||||
|
if (adapter.enable()) {
|
||||||
|
LOG.info("Enabling Bluetooth");
|
||||||
|
wasEnabledByUs = true;
|
||||||
|
} else {
|
||||||
|
LOG.info("Could not enable Bluetooth");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void disableAdapterIfEnabledByUs() {
|
||||||
|
if (isAdapterEnabled() && wasEnabledByUs) {
|
||||||
|
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||||
|
else LOG.info("Could not disable Bluetooth");
|
||||||
|
wasEnabledByUs = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setEnabledByUs() {
|
||||||
|
wasEnabledByUs = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
String getBluetoothAddress() {
|
||||||
|
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
||||||
|
return address.isEmpty() ? null : address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
BluetoothServerSocket openServerSocket(String uuid) throws IOException {
|
||||||
|
return adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||||
|
"RFCOMM", UUID.fromString(uuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void tryToClose(@Nullable BluetoothServerSocket ss) {
|
||||||
|
try {
|
||||||
|
if (ss != null) ss.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DuplexTransportConnection acceptConnection(BluetoothServerSocket ss)
|
||||||
|
throws IOException {
|
||||||
|
return wrapSocket(ss.accept());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||||
|
return new AndroidBluetoothTransportConnection(this, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isValidAddress(String address) {
|
||||||
|
return BluetoothAdapter.checkBluetoothAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DuplexTransportConnection connectTo(String address, String uuid)
|
||||||
|
throws IOException {
|
||||||
|
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||||
|
UUID u = UUID.fromString(uuid);
|
||||||
|
BluetoothSocket s = null;
|
||||||
|
try {
|
||||||
|
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
||||||
|
s.connect();
|
||||||
|
return wrapSocket(s);
|
||||||
|
} catch (IOException e) {
|
||||||
|
tryToClose(s);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(@Nullable Closeable c) {
|
||||||
|
try {
|
||||||
|
if (c != null) c.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctx, Intent intent) {
|
||||||
|
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||||
|
if (state == STATE_ON) onAdapterEnabled();
|
||||||
|
else if (state == STATE_OFF) onAdapterDisabled();
|
||||||
|
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||||
|
if (scanMode == SCAN_MODE_NONE) {
|
||||||
|
LOG.info("Scan mode: None");
|
||||||
|
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
||||||
|
LOG.info("Scan mode: Connectable");
|
||||||
|
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||||
|
LOG.info("Scan mode: Discoverable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.bramble.plugin.droidtooth;
|
package org.briarproject.bramble.plugin.bluetooth;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||||
|
|
||||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||||
@@ -35,7 +35,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
|||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
public DroidtoothPluginFactory(Executor ioExecutor,
|
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
||||||
AndroidExecutor androidExecutor, Context appContext,
|
AndroidExecutor androidExecutor, Context appContext,
|
||||||
SecureRandom secureRandom, EventBus eventBus,
|
SecureRandom secureRandom, EventBus eventBus,
|
||||||
BackoffFactory backoffFactory) {
|
BackoffFactory backoffFactory) {
|
||||||
@@ -61,7 +61,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
|||||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
|
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
|
||||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||||
MAX_LATENCY);
|
MAX_LATENCY);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.bramble.plugin.droidtooth;
|
package org.briarproject.bramble.plugin.bluetooth;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothSocket;
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
|
||||||
@@ -11,11 +11,12 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class DroidtoothTransportConnection extends AbstractDuplexTransportConnection {
|
class AndroidBluetoothTransportConnection
|
||||||
|
extends AbstractDuplexTransportConnection {
|
||||||
|
|
||||||
private final BluetoothSocket socket;
|
private final BluetoothSocket socket;
|
||||||
|
|
||||||
DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
}
|
}
|
||||||
@@ -1,490 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.droidtooth;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
|
||||||
import android.bluetooth.BluetoothServerSocket;
|
|
||||||
import android.bluetooth.BluetoothSocket;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginException;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
class DroidtoothPlugin implements DuplexPlugin, EventListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final AndroidExecutor androidExecutor;
|
|
||||||
private final Context appContext;
|
|
||||||
private final SecureRandom secureRandom;
|
|
||||||
private final Backoff backoff;
|
|
||||||
private final DuplexPluginCallback callback;
|
|
||||||
private final int maxLatency;
|
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private volatile boolean running = false;
|
|
||||||
private volatile boolean wasEnabledByUs = false;
|
|
||||||
private volatile BluetoothStateReceiver receiver = null;
|
|
||||||
private volatile BluetoothServerSocket socket = null;
|
|
||||||
|
|
||||||
// Non-null if the plugin started successfully
|
|
||||||
private volatile BluetoothAdapter adapter = null;
|
|
||||||
|
|
||||||
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
|
||||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
|
||||||
DuplexPluginCallback callback, int maxLatency) {
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.androidExecutor = androidExecutor;
|
|
||||||
this.appContext = appContext;
|
|
||||||
this.secureRandom = secureRandom;
|
|
||||||
this.backoff = backoff;
|
|
||||||
this.callback = callback;
|
|
||||||
this.maxLatency = maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportId getId() {
|
|
||||||
return ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxLatency() {
|
|
||||||
return maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxIdleTime() {
|
|
||||||
// Bluetooth detects dead connections so we don't need keepalives
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() throws PluginException {
|
|
||||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
|
||||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
|
||||||
// with a message queue, so submit it to the AndroidExecutor
|
|
||||||
try {
|
|
||||||
adapter = androidExecutor.runOnBackgroundThread(
|
|
||||||
BluetoothAdapter::getDefaultAdapter).get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
LOG.warning("Interrupted while getting BluetoothAdapter");
|
|
||||||
throw new PluginException(e);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
throw new PluginException(e);
|
|
||||||
}
|
|
||||||
if (adapter == null) {
|
|
||||||
LOG.info("Bluetooth is not supported");
|
|
||||||
throw new PluginException();
|
|
||||||
}
|
|
||||||
running = true;
|
|
||||||
// Listen for changes to the Bluetooth state
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(ACTION_STATE_CHANGED);
|
|
||||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
|
||||||
receiver = new BluetoothStateReceiver();
|
|
||||||
appContext.registerReceiver(receiver, filter);
|
|
||||||
// If Bluetooth is enabled, bind a socket
|
|
||||||
if (adapter.isEnabled()) {
|
|
||||||
bind();
|
|
||||||
} else {
|
|
||||||
// Enable Bluetooth if settings allow
|
|
||||||
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
|
|
||||||
enableAdapter();
|
|
||||||
} else {
|
|
||||||
LOG.info("Not enabling Bluetooth");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bind() {
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
if (!isRunning()) return;
|
|
||||||
String address = AndroidUtils.getBluetoothAddress(appContext,
|
|
||||||
adapter);
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Local address " + scrubMacAddress(address));
|
|
||||||
if (!StringUtils.isNullOrEmpty(address)) {
|
|
||||||
// Advertise the Bluetooth address to contacts
|
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
p.put(PROP_ADDRESS, address);
|
|
||||||
callback.mergeLocalProperties(p);
|
|
||||||
}
|
|
||||||
// Bind a server socket to accept connections from contacts
|
|
||||||
BluetoothServerSocket ss;
|
|
||||||
try {
|
|
||||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
|
||||||
"RFCOMM", getUuid());
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isRunning()) {
|
|
||||||
tryToClose(ss);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOG.info("Socket bound");
|
|
||||||
socket = ss;
|
|
||||||
backoff.reset();
|
|
||||||
callback.transportEnabled();
|
|
||||||
acceptContactConnections();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private UUID getUuid() {
|
|
||||||
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
|
||||||
if (uuid == null) {
|
|
||||||
byte[] random = new byte[UUID_BYTES];
|
|
||||||
secureRandom.nextBytes(random);
|
|
||||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
p.put(PROP_UUID, uuid);
|
|
||||||
callback.mergeLocalProperties(p);
|
|
||||||
}
|
|
||||||
return UUID.fromString(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable BluetoothServerSocket ss) {
|
|
||||||
try {
|
|
||||||
if (ss != null) ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
} finally {
|
|
||||||
callback.transportDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void acceptContactConnections() {
|
|
||||||
while (isRunning()) {
|
|
||||||
BluetoothSocket s;
|
|
||||||
try {
|
|
||||||
s = socket.accept();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// This is expected when the socket is closed
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
String address = s.getRemoteDevice().getAddress();
|
|
||||||
LOG.info("Connection from " + scrubMacAddress(address));
|
|
||||||
}
|
|
||||||
backoff.reset();
|
|
||||||
callback.incomingConnectionCreated(wrapSocket(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
|
||||||
return new DroidtoothTransportConnection(this, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableAdapter() {
|
|
||||||
if (adapter != null && !adapter.isEnabled()) {
|
|
||||||
if (adapter.enable()) {
|
|
||||||
LOG.info("Enabling Bluetooth");
|
|
||||||
wasEnabledByUs = true;
|
|
||||||
} else {
|
|
||||||
LOG.info("Could not enable Bluetooth");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
running = false;
|
|
||||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
|
||||||
tryToClose(socket);
|
|
||||||
disableAdapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disableAdapter() {
|
|
||||||
if (adapter != null && adapter.isEnabled() && wasEnabledByUs) {
|
|
||||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
|
||||||
else LOG.info("Could not disable Bluetooth");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRunning() {
|
|
||||||
return running && adapter != null && adapter.isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldPoll() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPollingInterval() {
|
|
||||||
return backoff.getPollingInterval();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void poll(Collection<ContactId> connected) {
|
|
||||||
if (!isRunning()) return;
|
|
||||||
backoff.increment();
|
|
||||||
// Try to connect to known devices in parallel
|
|
||||||
Map<ContactId, TransportProperties> remote =
|
|
||||||
callback.getRemoteProperties();
|
|
||||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
|
||||||
ContactId c = e.getKey();
|
|
||||||
if (connected.contains(c)) continue;
|
|
||||||
String address = e.getValue().get(PROP_ADDRESS);
|
|
||||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
|
||||||
String uuid = e.getValue().get(PROP_UUID);
|
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
if (!running) return;
|
|
||||||
BluetoothSocket s = connect(address, uuid);
|
|
||||||
if (s != null) {
|
|
||||||
backoff.reset();
|
|
||||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private BluetoothSocket connect(String address, String uuid) {
|
|
||||||
// Validate the address
|
|
||||||
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
|
||||||
if (LOG.isLoggable(WARNING))
|
|
||||||
// not scrubbing here to be able to figure out the problem
|
|
||||||
LOG.warning("Invalid address " + address);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Validate the UUID
|
|
||||||
UUID u;
|
|
||||||
try {
|
|
||||||
u = UUID.fromString(uuid);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Try to connect
|
|
||||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
|
||||||
BluetoothSocket s = null;
|
|
||||||
try {
|
|
||||||
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
|
||||||
s.connect();
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Connected to " + scrubMacAddress(address));
|
|
||||||
return s;
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Failed to connect to " + scrubMacAddress(address)
|
|
||||||
+ ": " + e);
|
|
||||||
}
|
|
||||||
tryToClose(s);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable Closeable c) {
|
|
||||||
try {
|
|
||||||
if (c != null) c.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createConnection(ContactId c) {
|
|
||||||
if (!isRunning()) return null;
|
|
||||||
TransportProperties p = callback.getRemoteProperties(c);
|
|
||||||
String address = p.get(PROP_ADDRESS);
|
|
||||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
|
||||||
String uuid = p.get(PROP_UUID);
|
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
|
||||||
BluetoothSocket s = connect(address, uuid);
|
|
||||||
if (s == null) return null;
|
|
||||||
return new DroidtoothTransportConnection(this, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsKeyAgreement() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
|
||||||
if (!isRunning()) return null;
|
|
||||||
// There's no point listening if we can't discover our own address
|
|
||||||
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
|
||||||
if (address.isEmpty()) return null;
|
|
||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
|
||||||
// Bind a server socket for receiving key agreement connections
|
|
||||||
BluetoothServerSocket ss;
|
|
||||||
try {
|
|
||||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
|
||||||
"RFCOMM", uuid);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
BdfList descriptor = new BdfList();
|
|
||||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
|
||||||
descriptor.add(StringUtils.macToBytes(address));
|
|
||||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
|
||||||
if (!isRunning()) return null;
|
|
||||||
String address;
|
|
||||||
try {
|
|
||||||
address = parseAddress(descriptor);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
LOG.info("Invalid address in key agreement descriptor");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
|
||||||
BluetoothSocket s = connect(address, uuid.toString());
|
|
||||||
if (s == null) return null;
|
|
||||||
return new DroidtoothTransportConnection(this, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
|
||||||
byte[] mac = descriptor.getRaw(1);
|
|
||||||
if (mac.length != 6) throw new FormatException();
|
|
||||||
return StringUtils.macToString(mac);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof EnableBluetoothEvent) {
|
|
||||||
enableAdapterAsync();
|
|
||||||
} else if (e instanceof DisableBluetoothEvent) {
|
|
||||||
disableAdapterAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableAdapterAsync() {
|
|
||||||
ioExecutor.execute(this::enableAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disableAdapterAsync() {
|
|
||||||
ioExecutor.execute(this::disableAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
|
||||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
|
||||||
if (state == STATE_ON) {
|
|
||||||
LOG.info("Bluetooth enabled");
|
|
||||||
bind();
|
|
||||||
} else if (state == STATE_OFF) {
|
|
||||||
LOG.info("Bluetooth disabled");
|
|
||||||
tryToClose(socket);
|
|
||||||
}
|
|
||||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
|
||||||
if (scanMode == SCAN_MODE_NONE) {
|
|
||||||
LOG.info("Scan mode: None");
|
|
||||||
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
|
||||||
LOG.info("Scan mode: Connectable");
|
|
||||||
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
|
||||||
LOG.info("Scan mode: Discoverable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
|
||||||
|
|
||||||
private final BluetoothServerSocket ss;
|
|
||||||
|
|
||||||
private BluetoothKeyAgreementListener(BdfList descriptor,
|
|
||||||
BluetoothServerSocket ss) {
|
|
||||||
super(descriptor);
|
|
||||||
this.ss = ss;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Callable<KeyAgreementConnection> listen() {
|
|
||||||
return () -> {
|
|
||||||
BluetoothSocket s = ss.accept();
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(ID.getString() + ": Incoming connection");
|
|
||||||
return new KeyAgreementConnection(
|
|
||||||
new DroidtoothTransportConnection(
|
|
||||||
DroidtoothPlugin.this, s), ID);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
try {
|
|
||||||
ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -59,7 +59,12 @@ import java.util.Map.Entry;
|
|||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
@@ -70,10 +75,15 @@ import javax.net.SocketFactory;
|
|||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static android.content.Context.POWER_SERVICE;
|
import static android.content.Context.POWER_SERVICE;
|
||||||
|
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||||
|
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||||
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||||
@@ -102,6 +112,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
Logger.getLogger(TorPlugin.class.getName());
|
Logger.getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final DevReporter reporter;
|
private final DevReporter reporter;
|
||||||
@@ -114,6 +125,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||||
private final File doneFile, cookieFile;
|
private final File doneFile, cookieFile;
|
||||||
private final PowerManager.WakeLock wakeLock;
|
private final PowerManager.WakeLock wakeLock;
|
||||||
|
private final Lock connectionStatusLock;
|
||||||
|
private final AtomicReference<Future<?>> connectivityCheck =
|
||||||
|
new AtomicReference<>();
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
@@ -122,12 +136,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private volatile TorControlConnection controlConnection = null;
|
private volatile TorControlConnection controlConnection = null;
|
||||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||||
|
|
||||||
TorPlugin(Executor ioExecutor, Context appContext,
|
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
||||||
LocationUtils locationUtils, DevReporter reporter,
|
Context appContext, LocationUtils locationUtils,
|
||||||
SocketFactory torSocketFactory, Backoff backoff,
|
DevReporter reporter, SocketFactory torSocketFactory,
|
||||||
DuplexPluginCallback callback, String architecture, int maxLatency,
|
Backoff backoff, DuplexPluginCallback callback,
|
||||||
int maxIdleTime) {
|
String architecture, int maxLatency, int maxIdleTime) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.scheduler = scheduler;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
@@ -152,6 +167,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
// This tag will prevent Huawei's powermanager from killing us.
|
// This tag will prevent Huawei's powermanager from killing us.
|
||||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||||
wakeLock.setReferenceCounted(false);
|
wakeLock.setReferenceCounted(false);
|
||||||
|
connectionStatusLock = new ReentrantLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -204,11 +220,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||||
while (stdout.hasNextLine() || stderr.hasNextLine()){
|
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
||||||
if(stdout.hasNextLine()) {
|
if (stdout.hasNextLine()) {
|
||||||
LOG.info(stdout.nextLine());
|
LOG.info(stdout.nextLine());
|
||||||
}
|
}
|
||||||
if(stderr.hasNextLine()){
|
if (stderr.hasNextLine()) {
|
||||||
LOG.info(stderr.nextLine());
|
LOG.info(stderr.nextLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +273,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
// Register to receive network status events
|
// Register to receive network status events
|
||||||
networkStateReceiver = new NetworkStateReceiver();
|
networkStateReceiver = new NetworkStateReceiver();
|
||||||
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(CONNECTIVITY_ACTION);
|
||||||
|
filter.addAction(ACTION_SCREEN_ON);
|
||||||
|
filter.addAction(ACTION_SCREEN_OFF);
|
||||||
|
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||||
appContext.registerReceiver(networkStateReceiver, filter);
|
appContext.registerReceiver(networkStateReceiver, filter);
|
||||||
// Bind a server socket to receive incoming hidden service connections
|
// Bind a server socket to receive incoming hidden service connections
|
||||||
bind();
|
bind();
|
||||||
@@ -594,7 +614,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,6 +638,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void orConnStatus(String status, String orName) {
|
public void orConnStatus(String status, String orName) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||||
|
if (status.equals("CLOSED") || status.equals("FAILED"))
|
||||||
|
updateConnectionStatus(); // Check whether we've lost connectivity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -657,7 +679,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(int event, String path) {
|
public void onEvent(int event, @Nullable String path) {
|
||||||
stopWatching();
|
stopWatching();
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
@@ -677,53 +699,73 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private void updateConnectionStatus() {
|
private void updateConnectionStatus() {
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
|
|
||||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
|
||||||
ConnectivityManager cm = (ConnectivityManager) o;
|
|
||||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
|
||||||
boolean online = net != null && net.isConnected();
|
|
||||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
|
||||||
String country = locationUtils.getCurrentCountry();
|
|
||||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
|
||||||
country);
|
|
||||||
Settings s = callback.getSettings();
|
|
||||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
|
||||||
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
|
||||||
if ("".equals(country)) LOG.info("Country code unknown");
|
|
||||||
else LOG.info("Country code: " + country);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!online) {
|
connectionStatusLock.lock();
|
||||||
LOG.info("Disabling network, device is offline");
|
updateConnectionStatusLocked();
|
||||||
enableNetwork(false);
|
} finally {
|
||||||
} else if (blocked) {
|
connectionStatusLock.unlock();
|
||||||
LOG.info("Disabling network, country is blocked");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
|
||||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
|
||||||
LOG.info("Disabling network due to data setting");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else {
|
|
||||||
LOG.info("Enabling network");
|
|
||||||
enableNetwork(true);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Locking: connectionStatusLock
|
||||||
|
private void updateConnectionStatusLocked() {
|
||||||
|
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||||
|
ConnectivityManager cm = (ConnectivityManager) o;
|
||||||
|
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||||
|
boolean online = net != null && net.isConnected();
|
||||||
|
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||||
|
String country = locationUtils.getCurrentCountry();
|
||||||
|
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||||
|
country);
|
||||||
|
Settings s = callback.getSettings();
|
||||||
|
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
||||||
|
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||||
|
if ("".equals(country)) LOG.info("Country code unknown");
|
||||||
|
else LOG.info("Country code: " + country);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!online) {
|
||||||
|
LOG.info("Disabling network, device is offline");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else if (blocked) {
|
||||||
|
LOG.info("Disabling network, country is blocked");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||||
|
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||||
|
LOG.info("Disabling network due to data setting");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else {
|
||||||
|
LOG.info("Enabling network");
|
||||||
|
enableNetwork(true);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleConnectionStatusUpdate() {
|
||||||
|
Future<?> newConnectivityCheck =
|
||||||
|
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||||
|
Future<?> oldConnectivityCheck =
|
||||||
|
connectivityCheck.getAndSet(newConnectivityCheck);
|
||||||
|
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
|
||||||
|
}
|
||||||
|
|
||||||
private class NetworkStateReceiver extends BroadcastReceiver {
|
private class NetworkStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context ctx, Intent i) {
|
public void onReceive(Context ctx, Intent i) {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
String action = i.getAction();
|
||||||
LOG.info("Detected connectivity change");
|
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
|
||||||
updateConnectionStatus();
|
updateConnectionStatus();
|
||||||
|
if (ACTION_SCREEN_ON.equals(action)
|
||||||
|
|| ACTION_SCREEN_OFF.equals(action)) {
|
||||||
|
scheduleConnectionStatusUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
@@ -36,6 +37,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private static final double BACKOFF_BASE = 1.2;
|
private static final double BACKOFF_BASE = 1.2;
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final DevReporter reporter;
|
private final DevReporter reporter;
|
||||||
@@ -43,11 +45,13 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final SocketFactory torSocketFactory;
|
private final SocketFactory torSocketFactory;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
public TorPluginFactory(Executor ioExecutor, Context appContext,
|
public TorPluginFactory(Executor ioExecutor,
|
||||||
|
ScheduledExecutorService scheduler, Context appContext,
|
||||||
LocationUtils locationUtils, DevReporter reporter,
|
LocationUtils locationUtils, DevReporter reporter,
|
||||||
EventBus eventBus, SocketFactory torSocketFactory,
|
EventBus eventBus, SocketFactory torSocketFactory,
|
||||||
BackoffFactory backoffFactory) {
|
BackoffFactory backoffFactory) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.scheduler = scheduler;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
@@ -89,9 +93,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
|
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
|
||||||
reporter, torSocketFactory, backoff, callback, architecture,
|
locationUtils, reporter, torSocketFactory, backoff, callback,
|
||||||
MAX_LATENCY, MAX_IDLE_TIME);
|
architecture, MAX_LATENCY, MAX_IDLE_TIME);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the database uses a newer schema than the current code.
|
||||||
|
*/
|
||||||
|
public class DataTooNewException extends DbException {
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the database uses an older schema than the current code and
|
||||||
|
* cannot be migrated.
|
||||||
|
*/
|
||||||
|
public class DataTooOldException extends DbException {
|
||||||
|
}
|
||||||
@@ -37,8 +37,13 @@ public interface DatabaseComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the database and returns true if the database already existed.
|
* Opens the database and returns true if the database already existed.
|
||||||
|
*
|
||||||
|
* @throws DataTooNewException if the data uses a newer schema than the
|
||||||
|
* current code
|
||||||
|
* @throws DataTooOldException if the data uses an older schema than the
|
||||||
|
* current code and cannot be migrated
|
||||||
*/
|
*/
|
||||||
boolean open() throws DbException;
|
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for any open transactions to finish and closes the database.
|
* Waits for any open transactions to finish and closes the database.
|
||||||
@@ -373,6 +378,16 @@ public interface DatabaseComponent {
|
|||||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||||
|
* message is due to be sent to the given contact. The returned value may
|
||||||
|
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
||||||
|
* no messages are scheduled to be sent.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all settings in the given namespace.
|
* Returns all settings in the given namespace.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
public interface MigrationListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called when a migration is started while opening the database.
|
||||||
|
* It will be called once for each migration being applied.
|
||||||
|
*/
|
||||||
|
void onMigrationRun();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.keyagreement;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An class for managing a particular key agreement listener.
|
* An class for managing a particular key agreement listener.
|
||||||
@@ -24,11 +24,11 @@ public abstract class KeyAgreementListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts listening for incoming connections, and returns a Callable that
|
* Blocks until an incoming connection is received and returns it.
|
||||||
* will return a KeyAgreementConnection when an incoming connection is
|
*
|
||||||
* received.
|
* @throws IOException if an error occurs or {@link #close()} is called.
|
||||||
*/
|
*/
|
||||||
public abstract Callable<KeyAgreementConnection> listen();
|
public abstract KeyAgreementConnection accept() throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the underlying server socket.
|
* Closes the underlying server socket.
|
||||||
|
|||||||
@@ -21,7 +21,25 @@ public interface LifecycleManager {
|
|||||||
* The result of calling {@link #startServices(String)}.
|
* The result of calling {@link #startServices(String)}.
|
||||||
*/
|
*/
|
||||||
enum StartResult {
|
enum StartResult {
|
||||||
ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
|
ALREADY_RUNNING,
|
||||||
|
DB_ERROR,
|
||||||
|
DATA_TOO_OLD_ERROR,
|
||||||
|
DATA_TOO_NEW_ERROR,
|
||||||
|
SERVICE_ERROR,
|
||||||
|
SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state the lifecycle can be in.
|
||||||
|
* Returned by {@link #getLifecycleState()}
|
||||||
|
*/
|
||||||
|
enum LifecycleState {
|
||||||
|
|
||||||
|
STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
|
||||||
|
|
||||||
|
public boolean isAfter(LifecycleState state) {
|
||||||
|
return ordinal() > state.ordinal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,4 +89,10 @@ public interface LifecycleManager {
|
|||||||
* the {@link DatabaseComponent} to be closed before returning.
|
* the {@link DatabaseComponent} to be closed before returning.
|
||||||
*/
|
*/
|
||||||
void waitForShutdown() throws InterruptedException;
|
void waitForShutdown() throws InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state of the lifecycle.
|
||||||
|
*/
|
||||||
|
LifecycleState getLifecycleState();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.briarproject.bramble.api.lifecycle.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when the app enters a new lifecycle state.
|
||||||
|
*/
|
||||||
|
public class LifecycleEvent extends Event {
|
||||||
|
|
||||||
|
private final LifecycleState state;
|
||||||
|
|
||||||
|
public LifecycleEvent(LifecycleState state) {
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LifecycleState getLifecycleState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.lifecycle.event;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event that is broadcast when the app is shutting down.
|
|
||||||
*/
|
|
||||||
public class ShutdownEvent extends Event {
|
|
||||||
}
|
|
||||||
@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to connect to the remote peer specified in the given descriptor.
|
* Attempts to connect to the remote peer specified in the given descriptor.
|
||||||
* Returns null if no connection can be established within the given time.
|
* Returns null if no connection can be established.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
DuplexTransportConnection createKeyAgreementConnection(
|
DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] remoteCommitment, BdfList descriptor, long timeout);
|
byte[] remoteCommitment, BdfList descriptor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.briarproject.bramble.api.plugin.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that informs the Bluetooth plugin that we have enabled the
|
||||||
|
* Bluetooth adapter.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class BluetoothEnabledEvent extends Event {
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event asks the Bluetooth plugin to enable the Bluetooth adapter.
|
* An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||||
*/
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ public class StringUtils {
|
|||||||
return toUtf8(s).length > maxLength;
|
return toUtf8(s).length > maxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isValidMac(String mac) {
|
||||||
|
return MAC.matcher(mac).matches();
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] macToBytes(String mac) {
|
public static byte[] macToBytes(String mac) {
|
||||||
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
||||||
return fromHexString(mac.replaceAll(":", ""));
|
return fromHexString(mac.replaceAll(":", ""));
|
||||||
|
|||||||
@@ -2,12 +2,27 @@ package org.briarproject.bramble.test;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.util.IoUtils;
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
|
||||||
private static final AtomicInteger nextTestDir =
|
private static final AtomicInteger nextTestDir =
|
||||||
@@ -38,4 +53,50 @@ public class TestUtils {
|
|||||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LocalAuthor getLocalAuthor() {
|
||||||
|
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalAuthor getLocalAuthor(int nameLength) {
|
||||||
|
AuthorId id = new AuthorId(getRandomId());
|
||||||
|
String name = getRandomString(nameLength);
|
||||||
|
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
long created = System.currentTimeMillis();
|
||||||
|
return new LocalAuthor(id, name, publicKey, privateKey, created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Author getAuthor() {
|
||||||
|
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Author getAuthor(int nameLength) {
|
||||||
|
AuthorId id = new AuthorId(getRandomId());
|
||||||
|
String name = getRandomString(nameLength);
|
||||||
|
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
return new Author(id, name, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Group getGroup(ClientId clientId) {
|
||||||
|
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||||
|
return getGroup(clientId, descriptorLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Group getGroup(ClientId clientId, int descriptorLength) {
|
||||||
|
GroupId groupId = new GroupId(getRandomId());
|
||||||
|
byte[] descriptor = getRandomBytes(descriptorLength);
|
||||||
|
return new Group(groupId, clientId, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message getMessage(GroupId groupId) {
|
||||||
|
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
|
||||||
|
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message getMessage(GroupId groupId, int rawLength) {
|
||||||
|
MessageId id = new MessageId(getRandomId());
|
||||||
|
byte[] raw = getRandomBytes(rawLength);
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
return new Message(id, groupId, timestamp, raw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package org.briarproject.bramble.db;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
@@ -37,8 +40,13 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the database and returns true if the database already existed.
|
* Opens the database and returns true if the database already existed.
|
||||||
|
*
|
||||||
|
* @throws DataTooNewException if the data uses a newer schema than the
|
||||||
|
* current code
|
||||||
|
* @throws DataTooOldException if the data uses an older schema than the
|
||||||
|
* current code and cannot be migrated
|
||||||
*/
|
*/
|
||||||
boolean open() throws DbException;
|
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevents new transactions from starting, waits for all current
|
* Prevents new transactions from starting, waits for all current
|
||||||
@@ -449,6 +457,16 @@ interface Database<T> {
|
|||||||
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||||
|
* message is due to be sent to the given contact. The returned value may
|
||||||
|
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
||||||
|
* if no messages are scheduled to be sent.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the message with the given ID, in serialised form, or null if
|
* Returns the message with the given ID, in serialised form, or null if
|
||||||
* the message has been deleted.
|
* the message has been deleted.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.db.ContactExistsException;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
||||||
@@ -90,8 +91,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
private final ReentrantReadWriteLock lock =
|
private final ReentrantReadWriteLock lock =
|
||||||
new ReentrantReadWriteLock(true);
|
new ReentrantReadWriteLock(true);
|
||||||
|
|
||||||
private volatile int shutdownHandle = -1;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
||||||
ShutdownManager shutdown) {
|
ShutdownManager shutdown) {
|
||||||
@@ -102,23 +101,22 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean open() throws DbException {
|
public boolean open(@Nullable MigrationListener listener)
|
||||||
Runnable shutdownHook = () -> {
|
throws DbException {
|
||||||
|
boolean reopened = db.open(listener);
|
||||||
|
shutdown.addShutdownHook(() -> {
|
||||||
try {
|
try {
|
||||||
close();
|
close();
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
boolean reopened = db.open();
|
|
||||||
shutdownHandle = shutdown.addShutdownHook(shutdownHook);
|
|
||||||
return reopened;
|
return reopened;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws DbException {
|
public void close() throws DbException {
|
||||||
if (closed.getAndSet(true)) return;
|
if (closed.getAndSet(true)) return;
|
||||||
shutdown.removeShutdownHook(shutdownHandle);
|
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,6 +581,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessageDependents(txn, m);
|
return db.getMessageDependents(txn, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
return db.getNextSendTime(txn, c);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Settings getSettings(Transaction transaction, String namespace)
|
public Settings getSettings(Transaction transaction, String namespace)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
|
|||||||
@@ -23,10 +23,4 @@ interface DatabaseConstants {
|
|||||||
*/
|
*/
|
||||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link Settings} key under which the minimum supported database
|
|
||||||
* schema version is stored.
|
|
||||||
*/
|
|
||||||
String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
|
|||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
@@ -13,6 +14,7 @@ import java.sql.DriverManager;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,10 +42,11 @@ class H2Database extends JdbcDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean open() throws DbException {
|
public boolean open(@Nullable MigrationListener listener)
|
||||||
|
throws DbException {
|
||||||
boolean reopen = config.databaseExists();
|
boolean reopen = config.databaseExists();
|
||||||
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
||||||
super.open("org.h2.Driver", reopen);
|
super.open("org.h2.Driver", reopen, listener);
|
||||||
return reopen;
|
return reopen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ package org.briarproject.bramble.db;
|
|||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||||
import org.briarproject.bramble.api.db.DbClosedException;
|
import org.briarproject.bramble.api.db.DbClosedException;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
@@ -47,6 +50,7 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
@@ -57,7 +61,6 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
|||||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
||||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.MIN_SCHEMA_VERSION_KEY;
|
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||||
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||||
|
|
||||||
@@ -68,8 +71,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
private static final int SCHEMA_VERSION = 30;
|
// Package access for testing
|
||||||
private static final int MIN_SCHEMA_VERSION = 30;
|
static final int CODE_SCHEMA_VERSION = 31;
|
||||||
|
|
||||||
private static final String CREATE_SETTINGS =
|
private static final String CREATE_SETTINGS =
|
||||||
"CREATE TABLE settings"
|
"CREATE TABLE settings"
|
||||||
@@ -148,11 +151,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private static final String CREATE_MESSAGE_METADATA =
|
private static final String CREATE_MESSAGE_METADATA =
|
||||||
"CREATE TABLE messageMetadata"
|
"CREATE TABLE messageMetadata"
|
||||||
+ " (messageId HASH NOT NULL,"
|
+ " (messageId HASH NOT NULL,"
|
||||||
|
+ " groupId HASH NOT NULL," // Denormalised
|
||||||
|
+ " state INT NOT NULL," // Denormalised
|
||||||
+ " key VARCHAR NOT NULL,"
|
+ " key VARCHAR NOT NULL,"
|
||||||
+ " value BINARY NOT NULL,"
|
+ " value BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (messageId, key),"
|
+ " PRIMARY KEY (messageId, key),"
|
||||||
+ " FOREIGN KEY (messageId)"
|
+ " FOREIGN KEY (messageId)"
|
||||||
+ " REFERENCES messages (messageId)"
|
+ " REFERENCES messages (messageId)"
|
||||||
|
+ " ON DELETE CASCADE,"
|
||||||
|
+ " FOREIGN KEY (groupId)"
|
||||||
|
+ " REFERENCES groups (groupId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
||||||
@@ -236,25 +244,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
||||||
+ " ON contacts (authorId)";
|
+ " ON contacts (authorId)";
|
||||||
|
|
||||||
private static final String INDEX_MESSAGES_BY_GROUP_ID =
|
|
||||||
"CREATE INDEX IF NOT EXISTS messagesByGroupId"
|
|
||||||
+ " ON messages (groupId)";
|
|
||||||
|
|
||||||
private static final String INDEX_OFFERS_BY_CONTACT_ID =
|
|
||||||
"CREATE INDEX IF NOT EXISTS offersByContactId"
|
|
||||||
+ " ON offers (contactId)";
|
|
||||||
|
|
||||||
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
||||||
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
||||||
+ " ON groups (clientId)";
|
+ " ON groups (clientId)";
|
||||||
|
|
||||||
private static final String INDEX_MESSAGE_METADATA_BY_MESSAGE_ID =
|
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
|
||||||
"CREATE INDEX IF NOT EXISTS messageMetadataByMessageId"
|
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||||
+ " ON messageMetadata (messageId)";
|
+ " ON messageMetadata (groupId, state)";
|
||||||
|
|
||||||
private static final String INDEX_GROUP_METADATA_BY_GROUP_ID =
|
|
||||||
"CREATE INDEX IF NOT EXISTS groupMetadataByGroupId"
|
|
||||||
+ " ON groupMetadata (groupId)";
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(JdbcDatabase.class.getName());
|
Logger.getLogger(JdbcDatabase.class.getName());
|
||||||
@@ -284,7 +280,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void open(String driverClass, boolean reopen) throws DbException {
|
protected void open(String driverClass, boolean reopen,
|
||||||
|
@Nullable MigrationListener listener) throws DbException {
|
||||||
// Load the JDBC driver
|
// Load the JDBC driver
|
||||||
try {
|
try {
|
||||||
Class.forName(driverClass);
|
Class.forName(driverClass);
|
||||||
@@ -295,10 +292,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
Connection txn = startTransaction();
|
Connection txn = startTransaction();
|
||||||
try {
|
try {
|
||||||
if (reopen) {
|
if (reopen) {
|
||||||
if (!checkSchemaVersion(txn)) throw new DbException();
|
checkSchemaVersion(txn, listener);
|
||||||
} else {
|
} else {
|
||||||
createTables(txn);
|
createTables(txn);
|
||||||
storeSchemaVersion(txn);
|
storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
|
||||||
}
|
}
|
||||||
createIndexes(txn);
|
createIndexes(txn);
|
||||||
commitTransaction(txn);
|
commitTransaction(txn);
|
||||||
@@ -308,19 +305,51 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkSchemaVersion(Connection txn) throws DbException {
|
/**
|
||||||
|
* Compares the schema version stored in the database with the schema
|
||||||
|
* version used by the current code and applies any suitable migrations to
|
||||||
|
* the data if necessary.
|
||||||
|
*
|
||||||
|
* @throws DataTooNewException if the data uses a newer schema than the
|
||||||
|
* current code
|
||||||
|
* @throws DataTooOldException if the data uses an older schema than the
|
||||||
|
* current code and cannot be migrated
|
||||||
|
*/
|
||||||
|
private void checkSchemaVersion(Connection txn,
|
||||||
|
@Nullable MigrationListener listener) throws DbException {
|
||||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||||
int schemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||||
if (schemaVersion == SCHEMA_VERSION) return true;
|
if (dataSchemaVersion == -1) throw new DbException();
|
||||||
if (schemaVersion < MIN_SCHEMA_VERSION) return false;
|
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return;
|
||||||
int minSchemaVersion = s.getInt(MIN_SCHEMA_VERSION_KEY, -1);
|
if (CODE_SCHEMA_VERSION < dataSchemaVersion)
|
||||||
return SCHEMA_VERSION >= minSchemaVersion;
|
throw new DataTooNewException();
|
||||||
|
// Apply any suitable migrations in order
|
||||||
|
for (Migration<Connection> m : getMigrations()) {
|
||||||
|
int start = m.getStartVersion(), end = m.getEndVersion();
|
||||||
|
if (start == dataSchemaVersion) {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Migrating from schema " + start + " to " + end);
|
||||||
|
if (listener != null) listener.onMigrationRun();
|
||||||
|
// Apply the migration
|
||||||
|
m.migrate(txn);
|
||||||
|
// Store the new schema version
|
||||||
|
storeSchemaVersion(txn, end);
|
||||||
|
dataSchemaVersion = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataSchemaVersion != CODE_SCHEMA_VERSION)
|
||||||
|
throw new DataTooOldException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeSchemaVersion(Connection txn) throws DbException {
|
// Package access for testing
|
||||||
|
List<Migration<Connection>> getMigrations() {
|
||||||
|
return Collections.singletonList(new Migration30_31());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeSchemaVersion(Connection txn, int version)
|
||||||
|
throws DbException {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.putInt(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
|
s.putInt(SCHEMA_VERSION_KEY, version);
|
||||||
s.putInt(MIN_SCHEMA_VERSION_KEY, MIN_SCHEMA_VERSION);
|
|
||||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,11 +399,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
s = txn.createStatement();
|
s = txn.createStatement();
|
||||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_GROUP_ID);
|
|
||||||
s.executeUpdate(INDEX_OFFERS_BY_CONTACT_ID);
|
|
||||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_MESSAGE_ID);
|
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
||||||
s.executeUpdate(INDEX_GROUP_METADATA_BY_GROUP_ID);
|
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s);
|
||||||
@@ -500,7 +526,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
// Create a contact row
|
// Create a contact row
|
||||||
String sql = "INSERT INTO contacts"
|
String sql = "INSERT INTO contacts"
|
||||||
+ " (authorId, name, publicKey, localAuthorId, verified, active)"
|
+ " (authorId, name, publicKey, localAuthorId,"
|
||||||
|
+ " verified, active)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, remote.getId().getBytes());
|
ps.setBytes(1, remote.getId().getBytes());
|
||||||
@@ -1330,16 +1357,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
// Retrieve the message IDs for each query term and intersect
|
// Retrieve the message IDs for each query term and intersect
|
||||||
Set<MessageId> intersection = null;
|
Set<MessageId> intersection = null;
|
||||||
String sql = "SELECT m.messageId"
|
String sql = "SELECT messageId FROM messageMetadata"
|
||||||
+ " FROM messages AS m"
|
+ " WHERE groupId = ? AND state = ?"
|
||||||
+ " JOIN messageMetadata AS md"
|
|
||||||
+ " ON m.messageId = md.messageId"
|
|
||||||
+ " WHERE state = ? AND groupId = ?"
|
|
||||||
+ " AND key = ? AND value = ?";
|
+ " AND key = ? AND value = ?";
|
||||||
for (Entry<String, byte[]> e : query.entrySet()) {
|
for (Entry<String, byte[]> e : query.entrySet()) {
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setBytes(1, g.getBytes());
|
||||||
ps.setBytes(2, g.getBytes());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
ps.setString(3, e.getKey());
|
ps.setString(3, e.getKey());
|
||||||
ps.setBytes(4, e.getValue());
|
ps.setBytes(4, e.getValue());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -1367,25 +1391,20 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT m.messageId, key, value"
|
String sql = "SELECT messageId, key, value"
|
||||||
+ " FROM messages AS m"
|
+ " FROM messageMetadata"
|
||||||
+ " JOIN messageMetadata AS md"
|
+ " WHERE groupId = ? AND state = ?";
|
||||||
+ " ON m.messageId = md.messageId"
|
|
||||||
+ " WHERE state = ? AND groupId = ?"
|
|
||||||
+ " ORDER BY m.messageId";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setBytes(1, g.getBytes());
|
||||||
ps.setBytes(2, g.getBytes());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
Map<MessageId, Metadata> all = new HashMap<>();
|
Map<MessageId, Metadata> all = new HashMap<>();
|
||||||
Metadata metadata = null;
|
|
||||||
MessageId lastMessageId = null;
|
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||||
if (lastMessageId == null || !messageId.equals(lastMessageId)) {
|
Metadata metadata = all.get(messageId);
|
||||||
|
if (metadata == null) {
|
||||||
metadata = new Metadata();
|
metadata = new Metadata();
|
||||||
all.put(messageId, metadata);
|
all.put(messageId, metadata);
|
||||||
lastMessageId = messageId;
|
|
||||||
}
|
}
|
||||||
metadata.put(rs.getString(2), rs.getBytes(3));
|
metadata.put(rs.getString(2), rs.getBytes(3));
|
||||||
}
|
}
|
||||||
@@ -1440,10 +1459,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT key, value FROM messageMetadata AS md"
|
String sql = "SELECT key, value FROM messageMetadata"
|
||||||
+ " JOIN messages AS m"
|
+ " WHERE state = ? AND messageId = ?";
|
||||||
+ " ON m.messageId = md.messageId"
|
|
||||||
+ " WHERE m.state = ? AND md.messageId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setInt(1, DELIVERED.getValue());
|
||||||
ps.setBytes(2, m.getBytes());
|
ps.setBytes(2, m.getBytes());
|
||||||
@@ -1466,11 +1483,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT key, value FROM messageMetadata AS md"
|
String sql = "SELECT key, value FROM messageMetadata"
|
||||||
+ " JOIN messages AS m"
|
+ " WHERE (state = ? OR state = ?)"
|
||||||
+ " ON m.messageId = md.messageId"
|
+ " AND messageId = ?";
|
||||||
+ " WHERE (m.state = ? OR m.state = ?)"
|
|
||||||
+ " AND md.messageId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setInt(1, DELIVERED.getValue());
|
||||||
ps.setInt(2, PENDING.getValue());
|
ps.setInt(2, PENDING.getValue());
|
||||||
@@ -1833,6 +1848,41 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextSendTime(Connection txn, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT expiry FROM messages AS m"
|
||||||
|
+ " JOIN groupVisibilities AS gv"
|
||||||
|
+ " ON m.groupId = gv.groupId"
|
||||||
|
+ " JOIN statuses AS s"
|
||||||
|
+ " ON m.messageId = s.messageId"
|
||||||
|
+ " AND gv.contactId = s.contactId"
|
||||||
|
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||||
|
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||||
|
+ " AND seen = FALSE"
|
||||||
|
+ " ORDER BY expiry LIMIT 1";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
long nextSendTime = Long.MAX_VALUE;
|
||||||
|
if (rs.next()) {
|
||||||
|
nextSendTime = rs.getLong(1);
|
||||||
|
if (rs.next()) throw new AssertionError();
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return nextSendTime;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public byte[] getRawMessage(Connection txn, MessageId m)
|
public byte[] getRawMessage(Connection txn, MessageId m)
|
||||||
@@ -2044,7 +2094,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
int[] batchAffected = ps.executeBatch();
|
int[] batchAffected = ps.executeBatch();
|
||||||
if (batchAffected.length != requested.size())
|
if (batchAffected.length != requested.size())
|
||||||
throw new DbStateException();
|
throw new DbStateException();
|
||||||
for (int rows: batchAffected) {
|
for (int rows : batchAffected) {
|
||||||
if (rows < 0) throw new DbStateException();
|
if (rows < 0) throw new DbStateException();
|
||||||
if (rows > 1) throw new DbStateException();
|
if (rows > 1) throw new DbStateException();
|
||||||
}
|
}
|
||||||
@@ -2058,25 +2108,92 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
@Override
|
@Override
|
||||||
public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta)
|
public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
mergeMetadata(txn, g.getBytes(), meta, "groupMetadata", "groupId");
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||||
|
g.getBytes(), meta, "groupMetadata", "groupId");
|
||||||
|
if (added.isEmpty()) return;
|
||||||
|
// Insert any keys that don't already exist
|
||||||
|
String sql = "INSERT INTO groupMetadata (groupId, key, value)"
|
||||||
|
+ " VALUES (?, ?, ?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, g.getBytes());
|
||||||
|
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||||
|
ps.setString(2, e.getKey());
|
||||||
|
ps.setBytes(3, e.getValue());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != added.size())
|
||||||
|
throw new DbStateException();
|
||||||
|
for (int rows : batchAffected)
|
||||||
|
if (rows != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mergeMessageMetadata(Connection txn, MessageId m, Metadata meta)
|
public void mergeMessageMetadata(Connection txn, MessageId m,
|
||||||
throws DbException {
|
Metadata meta) throws DbException {
|
||||||
mergeMetadata(txn, m.getBytes(), meta, "messageMetadata", "messageId");
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||||
|
m.getBytes(), meta, "messageMetadata", "messageId");
|
||||||
|
if (added.isEmpty()) return;
|
||||||
|
// Get the group ID and message state for the denormalised columns
|
||||||
|
String sql = "SELECT groupId, state FROM messages"
|
||||||
|
+ " WHERE messageId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) throw new DbStateException();
|
||||||
|
GroupId g = new GroupId(rs.getBytes(1));
|
||||||
|
State state = State.fromValue(rs.getInt(2));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
// Insert any keys that don't already exist
|
||||||
|
sql = "INSERT INTO messageMetadata"
|
||||||
|
+ " (messageId, groupId, state, key, value)"
|
||||||
|
+ " VALUES (?, ?, ?, ?, ?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
ps.setBytes(2, g.getBytes());
|
||||||
|
ps.setInt(3, state.getValue());
|
||||||
|
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||||
|
ps.setString(4, e.getKey());
|
||||||
|
ps.setBytes(5, e.getValue());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != added.size())
|
||||||
|
throw new DbStateException();
|
||||||
|
for (int rows : batchAffected)
|
||||||
|
if (rows != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeMetadata(Connection txn, byte[] id, Metadata meta,
|
// Removes or updates any existing entries, returns any entries that
|
||||||
String tableName, String columnName) throws DbException {
|
// need to be added
|
||||||
|
private Map<String, byte[]> removeOrUpdateMetadata(Connection txn,
|
||||||
|
byte[] id, Metadata meta, String tableName, String columnName)
|
||||||
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
// Determine which keys are being removed
|
// Determine which keys are being removed
|
||||||
List<String> removed = new ArrayList<>();
|
List<String> removed = new ArrayList<>();
|
||||||
Map<String, byte[]> retained = new HashMap<>();
|
Map<String, byte[]> notRemoved = new HashMap<>();
|
||||||
for (Entry<String, byte[]> e : meta.entrySet()) {
|
for (Entry<String, byte[]> e : meta.entrySet()) {
|
||||||
if (e.getValue() == REMOVE) removed.add(e.getKey());
|
if (e.getValue() == REMOVE) removed.add(e.getKey());
|
||||||
else retained.put(e.getKey(), e.getValue());
|
else notRemoved.put(e.getKey(), e.getValue());
|
||||||
}
|
}
|
||||||
// Delete any keys that are being removed
|
// Delete any keys that are being removed
|
||||||
if (!removed.isEmpty()) {
|
if (!removed.isEmpty()) {
|
||||||
@@ -2097,45 +2214,33 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
ps.close();
|
ps.close();
|
||||||
}
|
}
|
||||||
if (retained.isEmpty()) return;
|
if (notRemoved.isEmpty()) return Collections.emptyMap();
|
||||||
// Update any keys that already exist
|
// Update any keys that already exist
|
||||||
String sql = "UPDATE " + tableName + " SET value = ?"
|
String sql = "UPDATE " + tableName + " SET value = ?"
|
||||||
+ " WHERE " + columnName + " = ? AND key = ?";
|
+ " WHERE " + columnName + " = ? AND key = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(2, id);
|
ps.setBytes(2, id);
|
||||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||||
ps.setBytes(1, e.getValue());
|
ps.setBytes(1, e.getValue());
|
||||||
ps.setString(3, e.getKey());
|
ps.setString(3, e.getKey());
|
||||||
ps.addBatch();
|
ps.addBatch();
|
||||||
}
|
}
|
||||||
int[] batchAffected = ps.executeBatch();
|
int[] batchAffected = ps.executeBatch();
|
||||||
if (batchAffected.length != retained.size())
|
if (batchAffected.length != notRemoved.size())
|
||||||
throw new DbStateException();
|
throw new DbStateException();
|
||||||
for (int rows : batchAffected) {
|
for (int rows : batchAffected) {
|
||||||
if (rows < 0) throw new DbStateException();
|
if (rows < 0) throw new DbStateException();
|
||||||
if (rows > 1) throw new DbStateException();
|
if (rows > 1) throw new DbStateException();
|
||||||
}
|
}
|
||||||
// Insert any keys that don't already exist
|
|
||||||
sql = "INSERT INTO " + tableName
|
|
||||||
+ " (" + columnName + ", key, value)"
|
|
||||||
+ " VALUES (?, ?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
int updateIndex = 0, inserted = 0;
|
|
||||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
|
||||||
if (batchAffected[updateIndex] == 0) {
|
|
||||||
ps.setString(2, e.getKey());
|
|
||||||
ps.setBytes(3, e.getValue());
|
|
||||||
ps.addBatch();
|
|
||||||
inserted++;
|
|
||||||
}
|
|
||||||
updateIndex++;
|
|
||||||
}
|
|
||||||
batchAffected = ps.executeBatch();
|
|
||||||
if (batchAffected.length != inserted) throw new DbStateException();
|
|
||||||
for (int rows : batchAffected)
|
|
||||||
if (rows != 1) throw new DbStateException();
|
|
||||||
ps.close();
|
ps.close();
|
||||||
|
// Are there any keys that don't already exist?
|
||||||
|
Map<String, byte[]> added = new HashMap<>();
|
||||||
|
int updateIndex = 0;
|
||||||
|
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||||
|
if (batchAffected[updateIndex++] == 0)
|
||||||
|
added.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
return added;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -2488,7 +2593,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMessageShared(Connection txn, MessageId m) throws DbException {
|
public void setMessageShared(Connection txn, MessageId m)
|
||||||
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE messages SET shared = TRUE"
|
String sql = "UPDATE messages SET shared = TRUE"
|
||||||
@@ -2516,6 +2622,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
|
// Update denormalised column in messageMetadata
|
||||||
|
sql = "UPDATE messageMetadata SET state = ? WHERE messageId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, state.getValue());
|
||||||
|
ps.setBytes(2, m.getBytes());
|
||||||
|
affected = ps.executeUpdate();
|
||||||
|
if (affected < 0) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
|
||||||
|
interface Migration<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the schema version from which this migration starts.
|
||||||
|
*/
|
||||||
|
int getStartVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the schema version at which this migration ends.
|
||||||
|
*/
|
||||||
|
int getEndVersion();
|
||||||
|
|
||||||
|
void migrate(T txn) throws DbException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
|
class Migration30_31 implements Migration<Connection> {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(Migration30_31.class.getName());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStartVersion() {
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEndVersion() {
|
||||||
|
return 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(Connection txn) throws DbException {
|
||||||
|
Statement s = null;
|
||||||
|
try {
|
||||||
|
s = txn.createStatement();
|
||||||
|
// Add groupId column
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ADD COLUMN groupId BINARY(32) AFTER messageId");
|
||||||
|
// Populate groupId column
|
||||||
|
s.execute("UPDATE messageMetadata AS mm SET groupId ="
|
||||||
|
+ " (SELECT groupId FROM messages AS m"
|
||||||
|
+ " WHERE mm.messageId = m.messageId)");
|
||||||
|
// Add not null constraint now column has been populated
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ALTER COLUMN groupId"
|
||||||
|
+ " SET NOT NULL");
|
||||||
|
// Add foreign key constraint
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ADD CONSTRAINT groupIdForeignKey"
|
||||||
|
+ " FOREIGN KEY (groupId)"
|
||||||
|
+ " REFERENCES groups (groupId)"
|
||||||
|
+ " ON DELETE CASCADE");
|
||||||
|
// Add state column
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ADD COLUMN state INT AFTER groupId");
|
||||||
|
// Populate state column
|
||||||
|
s.execute("UPDATE messageMetadata AS mm SET state ="
|
||||||
|
+ " (SELECT state FROM messages AS m"
|
||||||
|
+ " WHERE mm.messageId = m.messageId)");
|
||||||
|
// Add not null constraint now column has been populated
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ALTER COLUMN state"
|
||||||
|
+ " SET NOT NULL");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(s);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(@Nullable Statement s) {
|
||||||
|
try {
|
||||||
|
if (s != null) s.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
interface ConnectionChooser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits a connection task to the chooser.
|
||||||
|
*/
|
||||||
|
void submit(Callable<KeyAgreementConnection> task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a connection returned by any of the tasks submitted to the
|
||||||
|
* chooser, waiting up to the given amount of time for a connection if
|
||||||
|
* necessary. Returns null if the time elapses without a connection
|
||||||
|
* becoming available.
|
||||||
|
*
|
||||||
|
* @param timeout the timeout in milliseconds
|
||||||
|
* @throws InterruptedException if the thread is interrupted while waiting
|
||||||
|
* for a connection to become available
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
KeyAgreementConnection poll(long timeout) throws InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the chooser. Any connections already returned to the chooser are
|
||||||
|
* closed unless they have been removed from the chooser by calling
|
||||||
|
* {@link #poll(long)}. Any connections subsequently returned to the
|
||||||
|
* chooser will also be closed.
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
@ThreadSafe
|
||||||
|
class ConnectionChooserImpl implements ConnectionChooser {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(ConnectionChooserImpl.class.getName());
|
||||||
|
|
||||||
|
private final Clock clock;
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
// The following are locking: lock
|
||||||
|
private boolean stopped = false;
|
||||||
|
private final Queue<KeyAgreementConnection> results = new LinkedList<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
|
||||||
|
this.clock = clock;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void submit(Callable<KeyAgreementConnection> task) {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
KeyAgreementConnection c = task.call();
|
||||||
|
if (c != null) addResult(c);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public KeyAgreementConnection poll(long timeout)
|
||||||
|
throws InterruptedException {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long end = now + timeout;
|
||||||
|
synchronized (lock) {
|
||||||
|
while (!stopped && results.isEmpty() && now < end) {
|
||||||
|
lock.wait(end - now);
|
||||||
|
now = clock.currentTimeMillis();
|
||||||
|
}
|
||||||
|
return results.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
List<KeyAgreementConnection> unused;
|
||||||
|
synchronized (lock) {
|
||||||
|
unused = new ArrayList<>(results);
|
||||||
|
results.clear();
|
||||||
|
stopped = true;
|
||||||
|
lock.notifyAll();
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Closing " + unused.size() + " unused connections");
|
||||||
|
for (KeyAgreementConnection c : unused) tryToClose(c.getConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addResult(KeyAgreementConnection c) {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Got connection for " + c.getTransportId());
|
||||||
|
boolean close = false;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (stopped) {
|
||||||
|
close = true;
|
||||||
|
} else {
|
||||||
|
results.add(c);
|
||||||
|
lock.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (close) {
|
||||||
|
LOG.info("Already stopped");
|
||||||
|
tryToClose(c.getConnection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(DuplexTransportConnection conn) {
|
||||||
|
try {
|
||||||
|
conn.getReader().dispose(false, true);
|
||||||
|
conn.getWriter().dispose(false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,23 +13,19 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.CompletionService;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.ExecutorCompletionService;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
||||||
@@ -45,29 +41,27 @@ class KeyAgreementConnector {
|
|||||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||||
|
|
||||||
private final Callbacks callbacks;
|
private final Callbacks callbacks;
|
||||||
private final Clock clock;
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
private final CompletionService<KeyAgreementConnection> connect;
|
private final ConnectionChooser connectionChooser;
|
||||||
|
|
||||||
private final List<KeyAgreementListener> listeners = new ArrayList<>();
|
private final List<KeyAgreementListener> listeners =
|
||||||
private final List<Future<KeyAgreementConnection>> pending =
|
new CopyOnWriteArrayList<>();
|
||||||
new ArrayList<>();
|
private final CountDownLatch aliceLatch = new CountDownLatch(1);
|
||||||
|
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean connecting = false;
|
private volatile boolean alice = false, stopped = false;
|
||||||
private volatile boolean alice = false;
|
|
||||||
|
|
||||||
KeyAgreementConnector(Callbacks callbacks, Clock clock,
|
KeyAgreementConnector(Callbacks callbacks,
|
||||||
CryptoComponent crypto, PluginManager pluginManager,
|
CryptoComponent crypto, PluginManager pluginManager,
|
||||||
Executor ioExecutor) {
|
ConnectionChooser connectionChooser) {
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
this.clock = clock;
|
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
connect = new ExecutorCompletionService<>(ioExecutor);
|
this.connectionChooser = connectionChooser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Payload listen(KeyPair localKeyPair) {
|
Payload listen(KeyPair localKeyPair) {
|
||||||
LOG.info("Starting BQP listeners");
|
LOG.info("Starting BQP listeners");
|
||||||
// Derive commitment
|
// Derive commitment
|
||||||
byte[] commitment = crypto.deriveKeyCommitment(
|
byte[] commitment = crypto.deriveKeyCommitment(
|
||||||
@@ -80,8 +74,9 @@ class KeyAgreementConnector {
|
|||||||
if (l != null) {
|
if (l != null) {
|
||||||
TransportId id = plugin.getId();
|
TransportId id = plugin.getId();
|
||||||
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
|
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
|
||||||
pending.add(connect.submit(new ReadableTask(l.listen())));
|
if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
|
||||||
listeners.add(l);
|
listeners.add(l);
|
||||||
|
connectionChooser.submit(new ReadableTask(l::accept));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Payload(commitment, descriptors);
|
return new Payload(commitment, descriptors);
|
||||||
@@ -89,125 +84,92 @@ class KeyAgreementConnector {
|
|||||||
|
|
||||||
void stopListening() {
|
void stopListening() {
|
||||||
LOG.info("Stopping BQP listeners");
|
LOG.info("Stopping BQP listeners");
|
||||||
for (KeyAgreementListener l : listeners) {
|
stopped = true;
|
||||||
l.close();
|
aliceLatch.countDown();
|
||||||
}
|
for (KeyAgreementListener l : listeners) l.close();
|
||||||
listeners.clear();
|
connectionChooser.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public KeyAgreementTransport connect(Payload remotePayload,
|
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
|
||||||
boolean alice) {
|
// Let the ReadableTasks know if we are Alice
|
||||||
// Let the listeners know if we are Alice
|
|
||||||
this.connecting = true;
|
|
||||||
this.alice = alice;
|
this.alice = alice;
|
||||||
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
aliceLatch.countDown();
|
||||||
|
|
||||||
// Start connecting over supported transports
|
// Start connecting over supported transports
|
||||||
LOG.info("Starting outgoing BQP connections");
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Starting outgoing BQP connections as "
|
||||||
|
+ (alice ? "Alice" : "Bob"));
|
||||||
|
}
|
||||||
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
||||||
Plugin p = pluginManager.getPlugin(d.getId());
|
Plugin p = pluginManager.getPlugin(d.getId());
|
||||||
if (p instanceof DuplexPlugin) {
|
if (p instanceof DuplexPlugin) {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting via " + d.getId());
|
||||||
DuplexPlugin plugin = (DuplexPlugin) p;
|
DuplexPlugin plugin = (DuplexPlugin) p;
|
||||||
pending.add(connect.submit(new ReadableTask(
|
byte[] commitment = remotePayload.getCommitment();
|
||||||
new ConnectorTask(plugin, remotePayload.getCommitment(),
|
BdfList descriptor = d.getDescriptor();
|
||||||
d.getDescriptor(), end))));
|
connectionChooser.submit(new ReadableTask(
|
||||||
|
new ConnectorTask(plugin, commitment, descriptor)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get chosen connection
|
// Get chosen connection
|
||||||
KeyAgreementConnection chosen = null;
|
|
||||||
try {
|
try {
|
||||||
long now = clock.currentTimeMillis();
|
KeyAgreementConnection chosen =
|
||||||
Future<KeyAgreementConnection> f =
|
connectionChooser.poll(CONNECTION_TIMEOUT);
|
||||||
connect.poll(end - now, MILLISECONDS);
|
if (chosen == null) return null;
|
||||||
if (f == null)
|
|
||||||
return null; // No task completed within the timeout.
|
|
||||||
chosen = f.get();
|
|
||||||
return new KeyAgreementTransport(chosen);
|
return new KeyAgreementTransport(chosen);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.info("Interrupted while waiting for connection");
|
LOG.info("Interrupted while waiting for connection");
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return null;
|
return null;
|
||||||
} catch (ExecutionException | IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
stopListening();
|
stopListening();
|
||||||
// Close all other connections
|
|
||||||
closePending(chosen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closePending(@Nullable KeyAgreementConnection chosen) {
|
private void waitingForAlice() {
|
||||||
for (Future<KeyAgreementConnection> f : pending) {
|
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
|
||||||
try {
|
|
||||||
if (f.cancel(true)) {
|
|
||||||
LOG.info("Cancelled task");
|
|
||||||
} else if (!f.isCancelled()) {
|
|
||||||
KeyAgreementConnection c = f.get();
|
|
||||||
if (c != null && c != chosen)
|
|
||||||
tryToClose(c.getConnection(), false);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Interrupted while closing sockets");
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return;
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
|
||||||
try {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Closing connection, exception: " + exception);
|
|
||||||
conn.getReader().dispose(exception, true);
|
|
||||||
conn.getWriter().dispose(exception);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
||||||
|
|
||||||
private final byte[] commitment;
|
private final byte[] commitment;
|
||||||
private final BdfList descriptor;
|
private final BdfList descriptor;
|
||||||
private final long end;
|
|
||||||
private final DuplexPlugin plugin;
|
private final DuplexPlugin plugin;
|
||||||
|
|
||||||
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
||||||
BdfList descriptor, long end) {
|
BdfList descriptor) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.commitment = commitment;
|
this.commitment = commitment;
|
||||||
this.descriptor = descriptor;
|
this.descriptor = descriptor;
|
||||||
this.end = end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementConnection call() throws Exception {
|
public KeyAgreementConnection call() throws Exception {
|
||||||
// Repeat attempts until we connect, get interrupted, or time out
|
// Repeat attempts until we connect, get stopped, or get interrupted
|
||||||
while (true) {
|
while (!stopped) {
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
if (now > end) throw new IOException();
|
|
||||||
DuplexTransportConnection conn =
|
DuplexTransportConnection conn =
|
||||||
plugin.createKeyAgreementConnection(commitment,
|
plugin.createKeyAgreementConnection(commitment,
|
||||||
descriptor, end - now);
|
descriptor);
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info(plugin.getId().getString() +
|
LOG.info(plugin.getId() + ": Outgoing connection");
|
||||||
": Outgoing connection");
|
|
||||||
return new KeyAgreementConnection(conn, plugin.getId());
|
return new KeyAgreementConnection(conn, plugin.getId());
|
||||||
}
|
}
|
||||||
// Wait 2s before retry (to circumvent transient failures)
|
// Wait 2s before retry (to circumvent transient failures)
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReadableTask
|
private class ReadableTask implements Callable<KeyAgreementConnection> {
|
||||||
implements Callable<KeyAgreementConnection> {
|
|
||||||
|
|
||||||
private final Callable<KeyAgreementConnection> connectionTask;
|
private final Callable<KeyAgreementConnection> connectionTask;
|
||||||
|
|
||||||
@@ -215,24 +177,23 @@ class KeyAgreementConnector {
|
|||||||
this.connectionTask = connectionTask;
|
this.connectionTask = connectionTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementConnection call() throws Exception {
|
public KeyAgreementConnection call() throws Exception {
|
||||||
KeyAgreementConnection c = connectionTask.call();
|
KeyAgreementConnection c = connectionTask.call();
|
||||||
|
if (c == null) return null;
|
||||||
|
aliceLatch.await();
|
||||||
|
if (alice || stopped) return c;
|
||||||
|
// Bob waits here for Alice to scan his QR code, determine her
|
||||||
|
// role, and send her key
|
||||||
InputStream in = c.getConnection().getReader().getInputStream();
|
InputStream in = c.getConnection().getReader().getInputStream();
|
||||||
boolean waitingSent = false;
|
while (!stopped && in.available() == 0) {
|
||||||
while (!alice && in.available() == 0) {
|
if (LOG.isLoggable(INFO))
|
||||||
if (!waitingSent && connecting && !alice) {
|
LOG.info(c.getTransportId() + ": Waiting for data");
|
||||||
// Bob waits here until Alice obtains his payload.
|
waitingForAlice();
|
||||||
callbacks.connectionWaiting();
|
Thread.sleep(500);
|
||||||
waitingSent = true;
|
|
||||||
}
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info(c.getTransportId().getString() +
|
|
||||||
": Waiting for connection");
|
|
||||||
}
|
|
||||||
Thread.sleep(1000);
|
|
||||||
}
|
}
|
||||||
if (!alice && LOG.isLoggable(INFO))
|
if (!stopped && LOG.isLoggable(INFO))
|
||||||
LOG.info(c.getTransportId().getString() + ": Data available");
|
LOG.info(c.getTransportId().getString() + ": Data available");
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
@@ -22,13 +13,9 @@ import dagger.Provides;
|
|||||||
public class KeyAgreementModule {
|
public class KeyAgreementModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(
|
||||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
|
KeyAgreementTaskFactoryImpl keyAgreementTaskFactory) {
|
||||||
CryptoComponent crypto, EventBus eventBus,
|
return keyAgreementTaskFactory;
|
||||||
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
|
|
||||||
PluginManager pluginManager) {
|
|
||||||
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
|
|
||||||
ioExecutor, payloadEncoder, pluginManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -40,4 +27,10 @@ public class KeyAgreementModule {
|
|||||||
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
|
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
|
||||||
return new PayloadParserImpl(bdfReaderFactory);
|
return new PayloadParserImpl(bdfReaderFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ConnectionChooser provideConnectionChooser(
|
||||||
|
ConnectionChooserImpl connectionChooser) {
|
||||||
|
return connectionChooser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ class KeyAgreementProtocol {
|
|||||||
byte[] theirPublicKey;
|
byte[] theirPublicKey;
|
||||||
if (alice) {
|
if (alice) {
|
||||||
sendKey();
|
sendKey();
|
||||||
// Alice waits here until Bob obtains her payload.
|
// Alice waits here for Bob to scan her QR code, determine his
|
||||||
|
// role, receive her key and respond with his key
|
||||||
callbacks.connectionWaiting();
|
callbacks.connectionWaiting();
|
||||||
theirPublicKey = receiveKey();
|
theirPublicKey = receiveKey();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,42 +5,37 @@ import org.briarproject.bramble.api.event.EventBus;
|
|||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
||||||
|
|
||||||
private final Clock clock;
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final PayloadEncoder payloadEncoder;
|
private final PayloadEncoder payloadEncoder;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
|
private final Provider<ConnectionChooser> connectionChooserProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
|
KeyAgreementTaskFactoryImpl(CryptoComponent crypto, EventBus eventBus,
|
||||||
EventBus eventBus, @IoExecutor Executor ioExecutor,
|
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||||
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
|
Provider<ConnectionChooser> connectionChooserProvider) {
|
||||||
this.clock = clock;
|
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.payloadEncoder = payloadEncoder;
|
this.payloadEncoder = payloadEncoder;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
|
this.connectionChooserProvider = connectionChooserProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementTask createTask() {
|
public KeyAgreementTask createTask() {
|
||||||
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
|
return new KeyAgreementTaskImpl(crypto, eventBus, payloadEncoder,
|
||||||
pluginManager, ioExecutor);
|
pluginManager, connectionChooserProvider.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,16 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class KeyAgreementTaskImpl extends Thread implements
|
class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||||
KeyAgreementTask, KeyAgreementConnector.Callbacks,
|
KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks {
|
||||||
KeyAgreementProtocol.Callbacks {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||||
@@ -43,15 +40,15 @@ class KeyAgreementTaskImpl extends Thread implements
|
|||||||
private Payload localPayload;
|
private Payload localPayload;
|
||||||
private Payload remotePayload;
|
private Payload remotePayload;
|
||||||
|
|
||||||
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
KeyAgreementTaskImpl(CryptoComponent crypto, EventBus eventBus,
|
||||||
EventBus eventBus, PayloadEncoder payloadEncoder,
|
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||||
PluginManager pluginManager, Executor ioExecutor) {
|
ConnectionChooser connectionChooser) {
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.payloadEncoder = payloadEncoder;
|
this.payloadEncoder = payloadEncoder;
|
||||||
localKeyPair = crypto.generateAgreementKeyPair();
|
localKeyPair = crypto.generateAgreementKeyPair();
|
||||||
connector = new KeyAgreementConnector(this, clock, crypto,
|
connector = new KeyAgreementConnector(this, crypto, pluginManager,
|
||||||
pluginManager, ioExecutor);
|
connectionChooser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,10 +62,8 @@ class KeyAgreementTaskImpl extends Thread implements
|
|||||||
@Override
|
@Override
|
||||||
public synchronized void stopListening() {
|
public synchronized void stopListening() {
|
||||||
if (localPayload != null) {
|
if (localPayload != null) {
|
||||||
if (remotePayload == null)
|
if (remotePayload == null) connector.stopListening();
|
||||||
connector.stopListening();
|
else interrupt();
|
||||||
else
|
|
||||||
interrupt();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package org.briarproject.bramble.lifecycle;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
@@ -12,7 +15,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.Service;
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Client;
|
import org.briarproject.bramble.api.sync.Client;
|
||||||
|
|
||||||
@@ -29,14 +32,21 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class LifecycleManagerImpl implements LifecycleManager {
|
class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LifecycleManagerImpl.class.getName());
|
Logger.getLogger(LifecycleManagerImpl.class.getName());
|
||||||
@@ -54,6 +64,8 @@ class LifecycleManagerImpl implements LifecycleManager {
|
|||||||
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
||||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
private volatile LifecycleState state = STARTING;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||||
CryptoComponent crypto, AuthorFactory authorFactory,
|
CryptoComponent crypto, AuthorFactory authorFactory,
|
||||||
@@ -119,7 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager {
|
|||||||
LOG.info("Starting services");
|
LOG.info("Starting services");
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
boolean reopened = db.open();
|
boolean reopened = db.open(this);
|
||||||
long duration = System.currentTimeMillis() - start;
|
long duration = System.currentTimeMillis() - start;
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
if (reopened)
|
if (reopened)
|
||||||
@@ -131,7 +143,10 @@ class LifecycleManagerImpl implements LifecycleManager {
|
|||||||
registerLocalAuthor(createLocalAuthor(nickname));
|
registerLocalAuthor(createLocalAuthor(nickname));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = STARTING_SERVICES;
|
||||||
dbLatch.countDown();
|
dbLatch.countDown();
|
||||||
|
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||||
|
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
for (Client c : clients) {
|
for (Client c : clients) {
|
||||||
@@ -157,8 +172,17 @@ class LifecycleManagerImpl implements LifecycleManager {
|
|||||||
+ " took " + duration + " ms");
|
+ " took " + duration + " ms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = RUNNING;
|
||||||
startupLatch.countDown();
|
startupLatch.countDown();
|
||||||
|
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
|
} catch (DataTooOldException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
return DATA_TOO_OLD_ERROR;
|
||||||
|
} catch (DataTooNewException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
return DATA_TOO_NEW_ERROR;
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return DB_ERROR;
|
return DB_ERROR;
|
||||||
@@ -170,6 +194,12 @@ class LifecycleManagerImpl implements LifecycleManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMigrationRun() {
|
||||||
|
state = MIGRATING_DATABASE;
|
||||||
|
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopServices() {
|
public void stopServices() {
|
||||||
try {
|
try {
|
||||||
@@ -180,7 +210,8 @@ class LifecycleManagerImpl implements LifecycleManager {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
LOG.info("Stopping services");
|
LOG.info("Stopping services");
|
||||||
eventBus.broadcast(new ShutdownEvent());
|
state = STOPPING;
|
||||||
|
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||||
for (Service s : services) {
|
for (Service s : services) {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
s.stopService();
|
s.stopService();
|
||||||
@@ -225,4 +256,8 @@ class LifecycleManagerImpl implements LifecycleManager {
|
|||||||
shutdownLatch.await();
|
shutdownLatch.await();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LifecycleState getLifecycleState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
@@ -25,6 +26,7 @@ import java.security.SecureRandom;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@@ -50,7 +52,7 @@ class Poller implements EventListener {
|
|||||||
private final SecureRandom random;
|
private final SecureRandom random;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final Lock lock;
|
private final Lock lock;
|
||||||
private final Map<TransportId, PollTask> tasks; // Locking: lock
|
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Poller(@IoExecutor Executor ioExecutor,
|
Poller(@IoExecutor Executor ioExecutor,
|
||||||
@@ -93,6 +95,10 @@ class Poller implements EventListener {
|
|||||||
TransportEnabledEvent t = (TransportEnabledEvent) e;
|
TransportEnabledEvent t = (TransportEnabledEvent) e;
|
||||||
// Poll the newly enabled transport
|
// Poll the newly enabled transport
|
||||||
pollNow(t.getTransportId());
|
pollNow(t.getTransportId());
|
||||||
|
} else if (e instanceof TransportDisabledEvent) {
|
||||||
|
TransportDisabledEvent t = (TransportDisabledEvent) e;
|
||||||
|
// Cancel polling for the disabled transport
|
||||||
|
cancel(t.getTransportId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,18 +157,31 @@ class Poller implements EventListener {
|
|||||||
TransportId t = p.getId();
|
TransportId t = p.getId();
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
PollTask scheduled = tasks.get(t);
|
ScheduledPollTask scheduled = tasks.get(t);
|
||||||
if (scheduled == null || due < scheduled.due) {
|
if (scheduled == null || due < scheduled.task.due) {
|
||||||
|
// If a later task exists, cancel it. If it's already started
|
||||||
|
// it will abort safely when it finds it's been replaced
|
||||||
|
if (scheduled != null) scheduled.future.cancel(false);
|
||||||
PollTask task = new PollTask(p, due, randomiseNext);
|
PollTask task = new PollTask(p, due, randomiseNext);
|
||||||
tasks.put(t, task);
|
Future future = scheduler.schedule(
|
||||||
scheduler.schedule(
|
|
||||||
() -> ioExecutor.execute(task), delay, MILLISECONDS);
|
() -> ioExecutor.execute(task), delay, MILLISECONDS);
|
||||||
|
tasks.put(t, new ScheduledPollTask(task, future));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cancel(TransportId t) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
ScheduledPollTask scheduled = tasks.remove(t);
|
||||||
|
if (scheduled != null) scheduled.future.cancel(false);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
private void poll(Plugin p) {
|
private void poll(Plugin p) {
|
||||||
TransportId t = p.getId();
|
TransportId t = p.getId();
|
||||||
@@ -170,6 +189,17 @@ class Poller implements EventListener {
|
|||||||
p.poll(connectionRegistry.getConnectedContacts(t));
|
p.poll(connectionRegistry.getConnectedContacts(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ScheduledPollTask {
|
||||||
|
|
||||||
|
private final PollTask task;
|
||||||
|
private final Future future;
|
||||||
|
|
||||||
|
private ScheduledPollTask(PollTask task, Future future) {
|
||||||
|
this.task = task;
|
||||||
|
this.future = future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class PollTask implements Runnable {
|
private class PollTask implements Runnable {
|
||||||
|
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
@@ -188,7 +218,9 @@ class Poller implements EventListener {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
TransportId t = plugin.getId();
|
TransportId t = plugin.getId();
|
||||||
if (tasks.get(t) != this) return; // Replaced by another task
|
ScheduledPollTask scheduled = tasks.get(t);
|
||||||
|
if (scheduled != null && scheduled.task != this)
|
||||||
|
return; // Replaced by another task
|
||||||
tasks.remove(t);
|
tasks.remove(t);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.briarproject.bramble.plugin.bluetooth;
|
|||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
@@ -13,8 +15,11 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.util.OsUtils;
|
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -23,30 +28,25 @@ import java.util.Collection;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.bluetooth.BluetoothStateException;
|
|
||||||
import javax.bluetooth.LocalDevice;
|
|
||||||
import javax.microedition.io.Connector;
|
|
||||||
import javax.microedition.io.StreamConnection;
|
|
||||||
import javax.microedition.io.StreamConnectionNotifier;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||||
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class BluetoothPlugin implements DuplexPlugin {
|
abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||||
@@ -58,9 +58,38 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
private final int maxLatency;
|
private final int maxLatency;
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false, contactConnections = false;
|
||||||
private volatile StreamConnectionNotifier socket = null;
|
private volatile String contactConnectionsUuid = null;
|
||||||
private volatile LocalDevice localDevice = null;
|
private volatile SS socket = null;
|
||||||
|
|
||||||
|
abstract void initialiseAdapter() throws IOException;
|
||||||
|
|
||||||
|
abstract boolean isAdapterEnabled();
|
||||||
|
|
||||||
|
abstract void enableAdapter();
|
||||||
|
|
||||||
|
abstract void disableAdapterIfEnabledByUs();
|
||||||
|
|
||||||
|
abstract void setEnabledByUs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the local Bluetooth address, or null if no valid address can
|
||||||
|
* be found.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
abstract String getBluetoothAddress();
|
||||||
|
|
||||||
|
abstract SS openServerSocket(String uuid) throws IOException;
|
||||||
|
|
||||||
|
abstract void tryToClose(@Nullable SS ss);
|
||||||
|
|
||||||
|
abstract DuplexTransportConnection acceptConnection(SS ss)
|
||||||
|
throws IOException;
|
||||||
|
|
||||||
|
abstract boolean isValidAddress(String address);
|
||||||
|
|
||||||
|
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||||
|
throws IOException;
|
||||||
|
|
||||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||||
@@ -71,6 +100,19 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onAdapterEnabled() {
|
||||||
|
LOG.info("Bluetooth enabled");
|
||||||
|
// We may not have been able to get the local address before
|
||||||
|
ioExecutor.execute(this::updateProperties);
|
||||||
|
if (shouldAllowContactConnections()) bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAdapterDisabled() {
|
||||||
|
LOG.info("Bluetooth disabled");
|
||||||
|
tryToClose(socket);
|
||||||
|
callback.transportDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransportId getId() {
|
public TransportId getId() {
|
||||||
return ID;
|
return ID;
|
||||||
@@ -90,107 +132,103 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void start() throws PluginException {
|
public void start() throws PluginException {
|
||||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
// Initialise the Bluetooth stack
|
|
||||||
try {
|
try {
|
||||||
localDevice = LocalDevice.getLocalDevice();
|
initialiseAdapter();
|
||||||
} catch (UnsatisfiedLinkError e) {
|
} catch (IOException e) {
|
||||||
// On Linux the user may need to install libbluetooth-dev
|
|
||||||
if (OsUtils.isLinux())
|
|
||||||
callback.showMessage("BLUETOOTH_INSTALL_LIBS");
|
|
||||||
throw new PluginException(e);
|
|
||||||
} catch (BluetoothStateException e) {
|
|
||||||
throw new PluginException(e);
|
throw new PluginException(e);
|
||||||
}
|
}
|
||||||
if (LOG.isLoggable(INFO))
|
updateProperties();
|
||||||
LOG.info("Local address " + localDevice.getBluetoothAddress());
|
|
||||||
running = true;
|
running = true;
|
||||||
bind();
|
loadSettings();
|
||||||
|
if (shouldAllowContactConnections()) {
|
||||||
|
if (isAdapterEnabled()) bind();
|
||||||
|
else enableAdapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSettings() {
|
||||||
|
contactConnections =
|
||||||
|
callback.getSettings().getBoolean(PREF_BT_ENABLE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldAllowContactConnections() {
|
||||||
|
return contactConnections;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bind() {
|
private void bind() {
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!running) return;
|
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||||
// Advertise the Bluetooth address to contacts
|
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
p.put(PROP_ADDRESS, localDevice.getBluetoothAddress());
|
|
||||||
callback.mergeLocalProperties(p);
|
|
||||||
// Bind a server socket to accept connections from contacts
|
// Bind a server socket to accept connections from contacts
|
||||||
String url = makeUrl("localhost", getUuid());
|
SS ss;
|
||||||
StreamConnectionNotifier ss;
|
|
||||||
try {
|
try {
|
||||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
ss = openServerSocket(contactConnectionsUuid);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
LOG.log(WARNING, e.toString(), e);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!running) {
|
if (!isRunning() || !shouldAllowContactConnections()) {
|
||||||
tryToClose(ss);
|
tryToClose(ss);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
socket = ss;
|
socket = ss;
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
callback.transportEnabled();
|
callback.transportEnabled();
|
||||||
acceptContactConnections(ss);
|
acceptContactConnections();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeUrl(String address, String uuid) {
|
private void updateProperties() {
|
||||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
TransportProperties p = callback.getLocalProperties();
|
||||||
}
|
String address = p.get(PROP_ADDRESS);
|
||||||
|
String uuid = p.get(PROP_UUID);
|
||||||
private String getUuid() {
|
boolean changed = false;
|
||||||
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
if (address == null) {
|
||||||
|
address = getBluetoothAddress();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Local address " + scrubMacAddress(address));
|
||||||
|
if (!StringUtils.isNullOrEmpty(address)) {
|
||||||
|
p.put(PROP_ADDRESS, address);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
byte[] random = new byte[UUID_BYTES];
|
byte[] random = new byte[UUID_BYTES];
|
||||||
secureRandom.nextBytes(random);
|
secureRandom.nextBytes(random);
|
||||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
p.put(PROP_UUID, uuid);
|
p.put(PROP_UUID, uuid);
|
||||||
callback.mergeLocalProperties(p);
|
changed = true;
|
||||||
}
|
}
|
||||||
return uuid;
|
contactConnectionsUuid = uuid;
|
||||||
|
if (changed) callback.mergeLocalProperties(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable StreamConnectionNotifier ss) {
|
private void acceptContactConnections() {
|
||||||
try {
|
|
||||||
if (ss != null) ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
} finally {
|
|
||||||
callback.transportDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void acceptContactConnections(StreamConnectionNotifier ss) {
|
|
||||||
while (true) {
|
while (true) {
|
||||||
StreamConnection s;
|
DuplexTransportConnection conn;
|
||||||
try {
|
try {
|
||||||
s = ss.acceptAndOpen();
|
conn = acceptConnection(socket);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// This is expected when the socket is closed
|
// This is expected when the socket is closed
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
callback.incomingConnectionCreated(wrapSocket(s));
|
callback.incomingConnectionCreated(conn);
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
|
||||||
return new BluetoothTransportConnection(this, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
tryToClose(socket);
|
tryToClose(socket);
|
||||||
|
callback.transportDisabled();
|
||||||
|
disableAdapterIfEnabledByUs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRunning() {
|
public boolean isRunning() {
|
||||||
return running;
|
return running && isAdapterEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -205,7 +243,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void poll(Collection<ContactId> connected) {
|
public void poll(Collection<ContactId> connected) {
|
||||||
if (!running) return;
|
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||||
backoff.increment();
|
backoff.increment();
|
||||||
// Try to connect to known devices in parallel
|
// Try to connect to known devices in parallel
|
||||||
Map<ContactId, TransportProperties> remote =
|
Map<ContactId, TransportProperties> remote =
|
||||||
@@ -218,41 +256,56 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
String uuid = e.getValue().get(PROP_UUID);
|
String uuid = e.getValue().get(PROP_UUID);
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!running) return;
|
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||||
StreamConnection s = connect(makeUrl(address, uuid));
|
DuplexTransportConnection conn = connect(address, uuid);
|
||||||
if (s != null) {
|
if (conn != null) {
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
callback.outgoingConnectionCreated(c, conn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private StreamConnection connect(String url) {
|
private DuplexTransportConnection connect(String address, String uuid) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + url);
|
// Validate the address
|
||||||
|
if (!isValidAddress(address)) {
|
||||||
|
if (LOG.isLoggable(WARNING))
|
||||||
|
// Not scrubbing here to be able to figure out the problem
|
||||||
|
LOG.warning("Invalid address " + address);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Validate the UUID
|
||||||
try {
|
try {
|
||||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
//noinspection ResultOfMethodCallIgnored
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Connected to " + url);
|
UUID.fromString(uuid);
|
||||||
return s;
|
} catch (IllegalArgumentException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||||
|
try {
|
||||||
|
DuplexTransportConnection conn = connectTo(address, uuid);
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connected to " + scrubMacAddress(address));
|
||||||
|
return conn;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Could not connect to " + url);
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Could not connect to " + scrubMacAddress(address));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createConnection(ContactId c) {
|
public DuplexTransportConnection createConnection(ContactId c) {
|
||||||
if (!running) return null;
|
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||||
TransportProperties p = callback.getRemoteProperties(c);
|
TransportProperties p = callback.getRemoteProperties(c);
|
||||||
String address = p.get(PROP_ADDRESS);
|
String address = p.get(PROP_ADDRESS);
|
||||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||||
String uuid = p.get(PROP_UUID);
|
String uuid = p.get(PROP_UUID);
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||||
String url = makeUrl(address, uuid);
|
return connect(address, uuid);
|
||||||
StreamConnection s = connect(url);
|
|
||||||
if (s == null) return null;
|
|
||||||
return new BluetoothTransportConnection(this, s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -262,35 +315,34 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||||
if (!running) return null;
|
if (!isRunning()) return null;
|
||||||
|
// There's no point listening if we can't discover our own address
|
||||||
|
String address = getBluetoothAddress();
|
||||||
|
if (address == null) return null;
|
||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
// No truncation necessary because COMMIT_LENGTH = 16
|
||||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||||
String url = makeUrl("localhost", uuid);
|
// Bind a server socket for receiving key agreement connections
|
||||||
// Make the device discoverable if possible
|
SS ss;
|
||||||
makeDeviceDiscoverable();
|
|
||||||
// Bind a server socket for receiving key agreementconnections
|
|
||||||
StreamConnectionNotifier ss;
|
|
||||||
try {
|
try {
|
||||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
ss = openServerSocket(uuid);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!running) {
|
if (!isRunning()) {
|
||||||
tryToClose(ss);
|
tryToClose(ss);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
BdfList descriptor = new BdfList();
|
BdfList descriptor = new BdfList();
|
||||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||||
String address = localDevice.getBluetoothAddress();
|
|
||||||
descriptor.add(StringUtils.macToBytes(address));
|
descriptor.add(StringUtils.macToBytes(address));
|
||||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
if (!isRunning()) return null;
|
if (!isRunning()) return null;
|
||||||
String address;
|
String address;
|
||||||
try {
|
try {
|
||||||
@@ -303,10 +355,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||||
String url = makeUrl(address, uuid);
|
return connect(address, uuid);
|
||||||
StreamConnection s = connect(url);
|
|
||||||
if (s == null) return null;
|
|
||||||
return new BluetoothTransportConnection(this, s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||||
@@ -315,44 +364,56 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return StringUtils.macToString(mac);
|
return StringUtils.macToString(mac);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeDeviceDiscoverable() {
|
@Override
|
||||||
// Try to make the device discoverable (requires root on Linux)
|
public void eventOccurred(Event e) {
|
||||||
try {
|
if (e instanceof EnableBluetoothEvent) {
|
||||||
localDevice.setDiscoverable(GIAC);
|
ioExecutor.execute(this::enableAdapter);
|
||||||
} catch (BluetoothStateException e) {
|
} else if (e instanceof DisableBluetoothEvent) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
ioExecutor.execute(this::disableAdapterIfEnabledByUs);
|
||||||
|
} else if (e instanceof BluetoothEnabledEvent) {
|
||||||
|
setEnabledByUs();
|
||||||
|
} else if (e instanceof SettingsUpdatedEvent) {
|
||||||
|
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||||
|
if (s.getNamespace().equals(ID.getString()))
|
||||||
|
ioExecutor.execute(this::onSettingsUpdated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSettingsUpdated() {
|
||||||
|
boolean wasAllowed = shouldAllowContactConnections();
|
||||||
|
loadSettings();
|
||||||
|
boolean isAllowed = shouldAllowContactConnections();
|
||||||
|
if (wasAllowed && !isAllowed) {
|
||||||
|
LOG.info("Contact connections disabled");
|
||||||
|
tryToClose(socket);
|
||||||
|
callback.transportDisabled();
|
||||||
|
disableAdapterIfEnabledByUs();
|
||||||
|
} else if (!wasAllowed && isAllowed) {
|
||||||
|
LOG.info("Contact connections enabled");
|
||||||
|
if (isAdapterEnabled()) bind();
|
||||||
|
else enableAdapter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||||
|
|
||||||
private final StreamConnectionNotifier ss;
|
private final SS ss;
|
||||||
|
|
||||||
private BluetoothKeyAgreementListener(BdfList descriptor,
|
private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) {
|
||||||
StreamConnectionNotifier ss) {
|
|
||||||
super(descriptor);
|
super(descriptor);
|
||||||
this.ss = ss;
|
this.ss = ss;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Callable<KeyAgreementConnection> listen() {
|
public KeyAgreementConnection accept() throws IOException {
|
||||||
return () -> {
|
DuplexTransportConnection conn = acceptConnection(ss);
|
||||||
StreamConnection s = ss.acceptAndOpen();
|
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||||
if (LOG.isLoggable(INFO))
|
return new KeyAgreementConnection(conn, ID);
|
||||||
LOG.info(ID.getString() + ": Incoming connection");
|
|
||||||
return new KeyAgreementConnection(
|
|
||||||
new BluetoothTransportConnection(
|
|
||||||
BluetoothPlugin.this, s), ID);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
try {
|
tryToClose(ss);
|
||||||
ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,9 +23,8 @@ import java.net.SocketAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -44,6 +43,9 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LanTcpPlugin.class.getName());
|
Logger.getLogger(LanTcpPlugin.class.getName());
|
||||||
|
|
||||||
|
private static final LanAddressComparator ADDRESS_COMPARATOR =
|
||||||
|
new LanAddressComparator();
|
||||||
|
|
||||||
private static final int MAX_ADDRESSES = 4;
|
private static final int MAX_ADDRESSES = 4;
|
||||||
private static final String SEPARATOR = ",";
|
private static final String SEPARATOR = ",";
|
||||||
|
|
||||||
@@ -63,19 +65,18 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
TransportProperties p = callback.getLocalProperties();
|
TransportProperties p = callback.getLocalProperties();
|
||||||
String oldIpPorts = p.get(PROP_IP_PORTS);
|
String oldIpPorts = p.get(PROP_IP_PORTS);
|
||||||
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
|
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
|
||||||
List<InetSocketAddress> locals = new LinkedList<>();
|
List<InetSocketAddress> locals = new ArrayList<>();
|
||||||
for (InetAddress local : getLocalIpAddresses()) {
|
for (InetAddress local : getLocalIpAddresses()) {
|
||||||
if (isAcceptableAddress(local)) {
|
if (isAcceptableAddress(local)) {
|
||||||
// If this is the old address, try to use the same port
|
// If this is the old address, try to use the same port
|
||||||
for (InetSocketAddress old : olds) {
|
for (InetSocketAddress old : olds) {
|
||||||
if (old.getAddress().equals(local)) {
|
if (old.getAddress().equals(local))
|
||||||
int port = old.getPort();
|
locals.add(new InetSocketAddress(local, old.getPort()));
|
||||||
locals.add(0, new InetSocketAddress(local, port));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
locals.add(new InetSocketAddress(local, 0));
|
locals.add(new InetSocketAddress(local, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Collections.sort(locals, ADDRESS_COMPARATOR);
|
||||||
return locals;
|
return locals;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,17 +154,39 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
// Package access for testing
|
// Package access for testing
|
||||||
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
|
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
|
||||||
// 10.0.0.0/8
|
// 10.0.0.0/8
|
||||||
if (localIp[0] == 10) return remoteIp[0] == 10;
|
if (isPrefix10(localIp)) return isPrefix10(remoteIp);
|
||||||
// 172.16.0.0/12
|
// 172.16.0.0/12
|
||||||
if (localIp[0] == (byte) 172 && (localIp[1] & 0xF0) == 16)
|
if (isPrefix172(localIp)) return isPrefix172(remoteIp);
|
||||||
return remoteIp[0] == (byte) 172 && (remoteIp[1] & 0xF0) == 16;
|
|
||||||
// 192.168.0.0/16
|
// 192.168.0.0/16
|
||||||
if (localIp[0] == (byte) 192 && localIp[1] == (byte) 168)
|
if (isPrefix192(localIp)) return isPrefix192(remoteIp);
|
||||||
return remoteIp[0] == (byte) 192 && remoteIp[1] == (byte) 168;
|
|
||||||
// Unrecognised prefix - may be compatible
|
// Unrecognised prefix - may be compatible
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isPrefix10(byte[] ipv4) {
|
||||||
|
return ipv4[0] == 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPrefix172(byte[] ipv4) {
|
||||||
|
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPrefix192(byte[] ipv4) {
|
||||||
|
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the prefix length for an RFC 1918 address, or 0 for any other
|
||||||
|
// address
|
||||||
|
private static int getRfc1918PrefixLength(InetAddress addr) {
|
||||||
|
if (!(addr instanceof Inet4Address)) return 0;
|
||||||
|
if (!addr.isSiteLocalAddress()) return 0;
|
||||||
|
byte[] ipv4 = addr.getAddress();
|
||||||
|
if (isPrefix10(ipv4)) return 8;
|
||||||
|
if (isPrefix172(ipv4)) return 12;
|
||||||
|
if (isPrefix192(ipv4)) return 16;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsKeyAgreement() {
|
public boolean supportsKeyAgreement() {
|
||||||
return true;
|
return true;
|
||||||
@@ -200,7 +223,7 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
if (!isRunning()) return null;
|
if (!isRunning()) return null;
|
||||||
InetSocketAddress remote;
|
InetSocketAddress remote;
|
||||||
try {
|
try {
|
||||||
@@ -259,14 +282,11 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Callable<KeyAgreementConnection> listen() {
|
public KeyAgreementConnection accept() throws IOException {
|
||||||
return () -> {
|
Socket s = ss.accept();
|
||||||
Socket s = ss.accept();
|
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||||
if (LOG.isLoggable(INFO))
|
return new KeyAgreementConnection(new TcpTransportConnection(
|
||||||
LOG.info(ID.getString() + ": Incoming connection");
|
LanTcpPlugin.this, s), ID);
|
||||||
return new KeyAgreementConnection(
|
|
||||||
new TcpTransportConnection(LanTcpPlugin.this, s), ID);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -278,4 +298,19 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class LanAddressComparator implements Comparator<InetSocketAddress> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(InetSocketAddress a, InetSocketAddress b) {
|
||||||
|
// Prefer addresses with non-zero ports
|
||||||
|
int aPort = a.getPort(), bPort = b.getPort();
|
||||||
|
if (aPort > 0 && bPort == 0) return -1;
|
||||||
|
if (aPort == 0 && bPort > 0) return 1;
|
||||||
|
// Prefer addresses with longer RFC 1918 prefixes
|
||||||
|
int aPrefix = getRfc1918PrefixLength(a.getAddress());
|
||||||
|
int bPrefix = getRfc1918PrefixLength(b.getAddress());
|
||||||
|
return bPrefix - aPrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createLocalState(Transaction txn) throws DbException {
|
public void createLocalState(Transaction txn) throws DbException {
|
||||||
|
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||||
db.addGroup(txn, localGroup);
|
db.addGroup(txn, localGroup);
|
||||||
// Ensure we've set things up for any pre-existing contacts
|
// Ensure we've set things up for any pre-existing contacts
|
||||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
|
|||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
@@ -29,6 +29,8 @@ import java.util.Collection;
|
|||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
@@ -36,6 +38,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
|||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
@@ -49,12 +52,14 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class DuplexOutgoingSession implements SyncSession, EventListener {
|
class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||||
|
|
||||||
// Check for retransmittable records once every 60 seconds
|
|
||||||
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
||||||
|
|
||||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {};
|
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
||||||
|
};
|
||||||
|
private static final ThrowingRunnable<IOException>
|
||||||
|
NEXT_SEND_TIME_DECREASED = () -> {
|
||||||
|
};
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
@@ -65,6 +70,13 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private final RecordWriter recordWriter;
|
private final RecordWriter recordWriter;
|
||||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||||
|
|
||||||
|
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean generateBatchQueued = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean generateRequestQueued =
|
||||||
|
new AtomicBoolean(false);
|
||||||
|
private final AtomicLong nextSendTime = new AtomicLong(Long.MAX_VALUE);
|
||||||
|
|
||||||
private volatile boolean interrupted = false;
|
private volatile boolean interrupted = false;
|
||||||
|
|
||||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
@@ -87,21 +99,21 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
eventBus.addListener(this);
|
eventBus.addListener(this);
|
||||||
try {
|
try {
|
||||||
// Start a query for each type of record
|
// Start a query for each type of record
|
||||||
dbExecutor.execute(new GenerateAck());
|
generateAck();
|
||||||
dbExecutor.execute(new GenerateBatch());
|
generateBatch();
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
dbExecutor.execute(new GenerateRequest());
|
generateRequest();
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long nextKeepalive = now + maxIdleTime;
|
long nextKeepalive = now + maxIdleTime;
|
||||||
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
|
||||||
boolean dataToFlush = true;
|
boolean dataToFlush = true;
|
||||||
// Write records until interrupted
|
// Write records until interrupted
|
||||||
try {
|
try {
|
||||||
while (!interrupted) {
|
while (!interrupted) {
|
||||||
// Work out how long we should wait for a record
|
// Work out how long we should wait for a record
|
||||||
now = clock.currentTimeMillis();
|
now = clock.currentTimeMillis();
|
||||||
long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
|
long keepaliveWait = Math.max(0, nextKeepalive - now);
|
||||||
if (wait < 0) wait = 0;
|
long sendWait = Math.max(0, nextSendTime.get() - now);
|
||||||
|
long wait = Math.min(keepaliveWait, sendWait);
|
||||||
// Flush any unflushed data if we're going to wait
|
// Flush any unflushed data if we're going to wait
|
||||||
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
|
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
|
||||||
recordWriter.flush();
|
recordWriter.flush();
|
||||||
@@ -113,20 +125,25 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
MILLISECONDS);
|
MILLISECONDS);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
now = clock.currentTimeMillis();
|
now = clock.currentTimeMillis();
|
||||||
if (now >= nextRetxQuery) {
|
if (now >= nextSendTime.get()) {
|
||||||
// Check for retransmittable records
|
// Check for retransmittable messages
|
||||||
dbExecutor.execute(new GenerateBatch());
|
LOG.info("Checking for retransmittable messages");
|
||||||
dbExecutor.execute(new GenerateOffer());
|
setNextSendTime(Long.MAX_VALUE);
|
||||||
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
generateBatch();
|
||||||
|
generateOffer();
|
||||||
}
|
}
|
||||||
if (now >= nextKeepalive) {
|
if (now >= nextKeepalive) {
|
||||||
// Flush the stream to keep it alive
|
// Flush the stream to keep it alive
|
||||||
|
LOG.info("Sending keepalive");
|
||||||
recordWriter.flush();
|
recordWriter.flush();
|
||||||
dataToFlush = false;
|
dataToFlush = false;
|
||||||
nextKeepalive = now + maxIdleTime;
|
nextKeepalive = now + maxIdleTime;
|
||||||
}
|
}
|
||||||
} else if (task == CLOSE) {
|
} else if (task == CLOSE) {
|
||||||
|
LOG.info("Closed");
|
||||||
break;
|
break;
|
||||||
|
} else if (task == NEXT_SEND_TIME_DECREASED) {
|
||||||
|
LOG.info("Next send time decreased");
|
||||||
} else {
|
} else {
|
||||||
task.run();
|
task.run();
|
||||||
dataToFlush = true;
|
dataToFlush = true;
|
||||||
@@ -142,6 +159,31 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void generateAck() {
|
||||||
|
if (generateAckQueued.compareAndSet(false, true))
|
||||||
|
dbExecutor.execute(new GenerateAck());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateBatch() {
|
||||||
|
if (generateBatchQueued.compareAndSet(false, true))
|
||||||
|
dbExecutor.execute(new GenerateBatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateOffer() {
|
||||||
|
if (generateOfferQueued.compareAndSet(false, true))
|
||||||
|
dbExecutor.execute(new GenerateOffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateRequest() {
|
||||||
|
if (generateRequestQueued.compareAndSet(false, true))
|
||||||
|
dbExecutor.execute(new GenerateRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setNextSendTime(long time) {
|
||||||
|
long old = nextSendTime.getAndSet(time);
|
||||||
|
if (time < old) writerTasks.add(NEXT_SEND_TIME_DECREASED);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interrupt() {
|
public void interrupt() {
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
@@ -154,22 +196,23 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
if (c.getContactId().equals(contactId)) interrupt();
|
if (c.getContactId().equals(contactId)) interrupt();
|
||||||
} else if (e instanceof MessageSharedEvent) {
|
} else if (e instanceof MessageSharedEvent) {
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||||
if (g.getAffectedContacts().contains(contactId))
|
if (g.getAffectedContacts().contains(contactId))
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
} else if (e instanceof MessageRequestedEvent) {
|
} else if (e instanceof MessageRequestedEvent) {
|
||||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||||
dbExecutor.execute(new GenerateBatch());
|
generateBatch();
|
||||||
} else if (e instanceof MessageToAckEvent) {
|
} else if (e instanceof MessageToAckEvent) {
|
||||||
if (((MessageToAckEvent) e).getContactId().equals(contactId))
|
if (((MessageToAckEvent) e).getContactId().equals(contactId))
|
||||||
dbExecutor.execute(new GenerateAck());
|
generateAck();
|
||||||
} else if (e instanceof MessageToRequestEvent) {
|
} else if (e instanceof MessageToRequestEvent) {
|
||||||
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
||||||
dbExecutor.execute(new GenerateRequest());
|
generateRequest();
|
||||||
} else if (e instanceof ShutdownEvent) {
|
} else if (e instanceof LifecycleEvent) {
|
||||||
interrupt();
|
LifecycleEvent l = (LifecycleEvent) e;
|
||||||
|
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +222,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
|
if (!generateAckQueued.getAndSet(false)) throw new AssertionError();
|
||||||
try {
|
try {
|
||||||
Ack a;
|
Ack a;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
@@ -212,7 +256,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeAck(ack);
|
recordWriter.writeAck(ack);
|
||||||
LOG.info("Sent ack");
|
LOG.info("Sent ack");
|
||||||
dbExecutor.execute(new GenerateAck());
|
generateAck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,12 +266,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
|
if (!generateBatchQueued.getAndSet(false))
|
||||||
|
throw new AssertionError();
|
||||||
try {
|
try {
|
||||||
Collection<byte[]> b;
|
Collection<byte[]> b;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
b = db.generateRequestedBatch(txn, contactId,
|
b = db.generateRequestedBatch(txn, contactId,
|
||||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||||
|
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
@@ -256,7 +303,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
||||||
LOG.info("Sent batch");
|
LOG.info("Sent batch");
|
||||||
dbExecutor.execute(new GenerateBatch());
|
generateBatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,12 +313,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
|
if (!generateOfferQueued.getAndSet(false))
|
||||||
|
throw new AssertionError();
|
||||||
try {
|
try {
|
||||||
Offer o;
|
Offer o;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
|
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
|
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
@@ -300,7 +350,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeOffer(offer);
|
recordWriter.writeOffer(offer);
|
||||||
LOG.info("Sent offer");
|
LOG.info("Sent offer");
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +360,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
|
if (!generateRequestQueued.getAndSet(false))
|
||||||
|
throw new AssertionError();
|
||||||
try {
|
try {
|
||||||
Request r;
|
Request r;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
@@ -343,7 +395,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeRequest(request);
|
recordWriter.writeRequest(request);
|
||||||
LOG.info("Sent request");
|
LOG.info("Sent request");
|
||||||
dbExecutor.execute(new GenerateRequest());
|
generateRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
|
|||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
@@ -27,6 +27,7 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An incoming {@link SyncSession}.
|
* An incoming {@link SyncSession}.
|
||||||
@@ -96,8 +97,9 @@ class IncomingSession implements SyncSession, EventListener {
|
|||||||
if (e instanceof ContactRemovedEvent) {
|
if (e instanceof ContactRemovedEvent) {
|
||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
if (c.getContactId().equals(contactId)) interrupt();
|
if (c.getContactId().equals(contactId)) interrupt();
|
||||||
} else if (e instanceof ShutdownEvent) {
|
} else if (e instanceof LifecycleEvent) {
|
||||||
interrupt();
|
LifecycleEvent l = (LifecycleEvent) e;
|
||||||
|
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
|
|||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||||
@@ -28,6 +28,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
@@ -109,8 +110,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (e instanceof ContactRemovedEvent) {
|
if (e instanceof ContactRemovedEvent) {
|
||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
if (c.getContactId().equals(contactId)) interrupt();
|
if (c.getContactId().equals(contactId)) interrupt();
|
||||||
} else if (e instanceof ShutdownEvent) {
|
} else if (e instanceof LifecycleEvent) {
|
||||||
interrupt();
|
LifecycleEvent l = (LifecycleEvent) e;
|
||||||
|
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,11 +47,10 @@ import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
|||||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -74,7 +73,13 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class DatabaseComponentImplTest extends BrambleTestCase {
|
public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private final Database<Object> database = context.mock(Database.class);
|
||||||
|
private final ShutdownManager shutdown =
|
||||||
|
context.mock(ShutdownManager.class);
|
||||||
|
private final EventBus eventBus = context.mock(EventBus.class);
|
||||||
|
|
||||||
private final Object txn = new Object();
|
private final Object txn = new Object();
|
||||||
private final ClientId clientId;
|
private final ClientId clientId;
|
||||||
@@ -125,16 +130,11 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void testSimpleCalls() throws Exception {
|
public void testSimpleCalls() throws Exception {
|
||||||
int shutdownHandle = 12345;
|
int shutdownHandle = 12345;
|
||||||
Mockery context = new Mockery();
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// open()
|
// open()
|
||||||
oneOf(database).open();
|
oneOf(database).open(null);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||||
will(returnValue(shutdownHandle));
|
will(returnValue(shutdownHandle));
|
||||||
@@ -194,13 +194,12 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
// endTransaction()
|
// endTransaction()
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
// close()
|
// close()
|
||||||
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
|
||||||
oneOf(database).close();
|
oneOf(database).close();
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
shutdown);
|
shutdown);
|
||||||
|
|
||||||
assertFalse(db.open());
|
assertFalse(db.open(null));
|
||||||
Transaction transaction = db.startTransaction(false);
|
Transaction transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.addLocalAuthor(transaction, localAuthor);
|
db.addLocalAuthor(transaction, localAuthor);
|
||||||
@@ -221,18 +220,11 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
db.close();
|
db.close();
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocalMessagesAreNotStoredUnlessGroupExists()
|
public void testLocalMessagesAreNotStoredUnlessGroupExists()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -252,17 +244,10 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddLocalMessage() throws Exception {
|
public void testAddLocalMessage() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -294,18 +279,11 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVariousMethodsThrowExceptionIfContactIsMissing()
|
public void testVariousMethodsThrowExceptionIfContactIsMissing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the contact is in the DB (which it's not)
|
// Check whether the contact is in the DB (which it's not)
|
||||||
exactly(18).of(database).startTransaction();
|
exactly(18).of(database).startTransaction();
|
||||||
@@ -500,18 +478,11 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing()
|
public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the pseudonym is in the DB (which it's not)
|
// Check whether the pseudonym is in the DB (which it's not)
|
||||||
exactly(3).of(database).startTransaction();
|
exactly(3).of(database).startTransaction();
|
||||||
@@ -552,18 +523,11 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVariousMethodsThrowExceptionIfGroupIsMissing()
|
public void testVariousMethodsThrowExceptionIfGroupIsMissing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the group is in the DB (which it's not)
|
// Check whether the group is in the DB (which it's not)
|
||||||
exactly(8).of(database).startTransaction();
|
exactly(8).of(database).startTransaction();
|
||||||
@@ -657,18 +621,11 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVariousMethodsThrowExceptionIfMessageIsMissing()
|
public void testVariousMethodsThrowExceptionIfMessageIsMissing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the message is in the DB (which it's not)
|
// Check whether the message is in the DB (which it's not)
|
||||||
exactly(11).of(database).startTransaction();
|
exactly(11).of(database).startTransaction();
|
||||||
@@ -792,18 +749,11 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVariousMethodsThrowExceptionIfTransportIsMissing()
|
public void testVariousMethodsThrowExceptionIfTransportIsMissing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// startTransaction()
|
// startTransaction()
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
@@ -890,19 +840,12 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateAck() throws Exception {
|
public void testGenerateAck() throws Exception {
|
||||||
Collection<MessageId> messagesToAck = Arrays.asList(messageId,
|
Collection<MessageId> messagesToAck = Arrays.asList(messageId,
|
||||||
messageId1);
|
messageId1);
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -925,8 +868,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -934,11 +875,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
byte[] raw1 = new byte[size];
|
byte[] raw1 = new byte[size];
|
||||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||||
Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -969,19 +905,12 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateOffer() throws Exception {
|
public void testGenerateOffer() throws Exception {
|
||||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1007,19 +936,12 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateRequest() throws Exception {
|
public void testGenerateRequest() throws Exception {
|
||||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1042,8 +964,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1051,11 +971,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
byte[] raw1 = new byte[size];
|
byte[] raw1 = new byte[size];
|
||||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||||
Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1087,17 +1002,10 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReceiveAck() throws Exception {
|
public void testReceiveAck() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1120,17 +1028,10 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReceiveMessage() throws Exception {
|
public void testReceiveMessage() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1175,17 +1076,10 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReceiveDuplicateMessage() throws Exception {
|
public void testReceiveDuplicateMessage() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1212,17 +1106,10 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReceiveMessageWithoutVisibleGroup() throws Exception {
|
public void testReceiveMessageWithoutVisibleGroup() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1242,8 +1129,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1251,11 +1136,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1296,17 +1176,10 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReceiveRequest() throws Exception {
|
public void testReceiveRequest() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1330,17 +1203,10 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testChangingVisibilityCallsListeners() throws Exception {
|
public void testChangingVisibilityCallsListeners() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1370,18 +1236,11 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNotChangingVisibilityDoesNotCallListeners()
|
public void testNotChangingVisibilityDoesNotCallListeners()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1403,8 +1262,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1412,11 +1269,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
TransportKeys transportKeys = createTransportKeys();
|
TransportKeys transportKeys = createTransportKeys();
|
||||||
Map<ContactId, TransportKeys> keys = Collections.singletonMap(
|
Map<ContactId, TransportKeys> keys = Collections.singletonMap(
|
||||||
contactId, transportKeys);
|
contactId, transportKeys);
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// startTransaction()
|
// startTransaction()
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
@@ -1446,8 +1298,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportKeys createTransportKeys() {
|
private TransportKeys createTransportKeys() {
|
||||||
@@ -1480,11 +1330,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
Settings merged = new Settings();
|
Settings merged = new Settings();
|
||||||
merged.put("foo", "bar");
|
merged.put("foo", "bar");
|
||||||
merged.put("baz", "qux");
|
merged.put("baz", "qux");
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// startTransaction()
|
// startTransaction()
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
@@ -1514,8 +1359,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
@@ -1545,12 +1388,6 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
private void testCannotStartTransactionDuringTransaction(
|
private void testCannotStartTransactionDuringTransaction(
|
||||||
boolean firstTxnReadOnly, boolean secondTxnReadOnly)
|
boolean firstTxnReadOnly, boolean secondTxnReadOnly)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1560,22 +1397,12 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
shutdown);
|
shutdown);
|
||||||
|
|
||||||
assertNotNull(db.startTransaction(firstTxnReadOnly));
|
assertNotNull(db.startTransaction(firstTxnReadOnly));
|
||||||
try {
|
db.startTransaction(secondTxnReadOnly);
|
||||||
db.startTransaction(secondTxnReadOnly);
|
fail();
|
||||||
fail();
|
|
||||||
} finally {
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCannotAddLocalIdentityAsContact() throws Exception {
|
public void testCannotAddLocalIdentityAsContact() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1599,18 +1426,10 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCannotAddDuplicateContact() throws Exception {
|
public void testCannotAddDuplicateContact() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1636,22 +1455,16 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testMessageDependencies() throws Exception {
|
public void testMessageDependencies() throws Exception {
|
||||||
int shutdownHandle = 12345;
|
int shutdownHandle = 12345;
|
||||||
Mockery context = new Mockery();
|
|
||||||
Database<Object> database = context.mock(Database.class);
|
|
||||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// open()
|
// open()
|
||||||
oneOf(database).open();
|
oneOf(database).open(null);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||||
will(returnValue(shutdownHandle));
|
will(returnValue(shutdownHandle));
|
||||||
@@ -1693,13 +1506,12 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
// endTransaction()
|
// endTransaction()
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
// close()
|
// close()
|
||||||
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
|
||||||
oneOf(database).close();
|
oneOf(database).close();
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
shutdown);
|
shutdown);
|
||||||
|
|
||||||
assertFalse(db.open());
|
assertFalse(db.open(null));
|
||||||
Transaction transaction = db.startTransaction(false);
|
Transaction transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.addLocalMessage(transaction, message, metadata, true);
|
db.addLocalMessage(transaction, message, metadata, true);
|
||||||
@@ -1714,7 +1526,5 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
|||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
db.close();
|
db.close();
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.system.SystemClock;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.TestDatabaseConfig;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||||
|
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||||
|
import static org.briarproject.bramble.db.JdbcDatabase.CODE_SCHEMA_VERSION;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private final Migration<Connection> migration =
|
||||||
|
context.mock(Migration.class, "migration");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private final Migration<Connection> migration1 =
|
||||||
|
context.mock(Migration.class, "migration1");
|
||||||
|
|
||||||
|
protected final DatabaseConfig config =
|
||||||
|
new TestDatabaseConfig(testDir, 1024 * 1024);
|
||||||
|
protected final Clock clock = new SystemClock();
|
||||||
|
|
||||||
|
abstract Database<Connection> createDatabase(
|
||||||
|
List<Migration<Connection>> migrations) throws Exception;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoesNotRunMigrationsWhenCreatingDatabase()
|
||||||
|
throws Exception {
|
||||||
|
Database<Connection> db = createDatabase(singletonList(migration));
|
||||||
|
assertFalse(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DbException.class)
|
||||||
|
public void testThrowsExceptionIfDataSchemaVersionIsMissing()
|
||||||
|
throws Exception {
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, -1);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - an exception should be thrown
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
db.open(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoesNotRunMigrationsIfSchemaVersionsMatch()
|
||||||
|
throws Exception {
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - migrations should not be run
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
assertTrue(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DataTooNewException.class)
|
||||||
|
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - an exception should be thrown
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
db.open(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DataTooOldException.class)
|
||||||
|
public void testThrowsExceptionIfCodeIsNewerThanDataAndNoMigrations()
|
||||||
|
throws Exception {
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(emptyList());
|
||||||
|
assertFalse(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - an exception should be thrown
|
||||||
|
db = createDatabase(emptyList());
|
||||||
|
db.open(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DataTooOldException.class)
|
||||||
|
public void testThrowsExceptionIfCodeIsNewerThanDataAndNoSuitableMigration()
|
||||||
|
throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(migration).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||||
|
oneOf(migration).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration1).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration1).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION));
|
||||||
|
}});
|
||||||
|
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - an exception should be thrown
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
db.open(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunsMigrationIfCodeIsNewerThanDataAndSuitableMigration()
|
||||||
|
throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// First migration should be run, increasing schema version by 2
|
||||||
|
oneOf(migration).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||||
|
oneOf(migration).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION));
|
||||||
|
oneOf(migration).migrate(with(any(Connection.class)));
|
||||||
|
// Second migration is not suitable and should be skipped
|
||||||
|
oneOf(migration1).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration1).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION));
|
||||||
|
}});
|
||||||
|
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - the first migration should be run
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
assertTrue(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunsMigrationsIfCodeIsNewerThanDataAndSuitableMigrations()
|
||||||
|
throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// First migration should be run, incrementing schema version
|
||||||
|
oneOf(migration).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||||
|
oneOf(migration).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration).migrate(with(any(Connection.class)));
|
||||||
|
// Second migration should be run, incrementing schema version again
|
||||||
|
oneOf(migration1).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration1).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION));
|
||||||
|
oneOf(migration1).migrate(with(any(Connection.class)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - both migrations should be run
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
assertTrue(db.open(null));
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDataSchemaVersion(Database<Connection> db)
|
||||||
|
throws Exception {
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
Settings s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
return s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDataSchemaVersion(Database<Connection> db, int version)
|
||||||
|
throws Exception {
|
||||||
|
Settings s = new Settings();
|
||||||
|
s.putInt(SCHEMA_VERSION_KEY, version);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.sync.Message;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
@@ -1635,6 +1636,53 @@ public class H2DatabaseTest extends BrambleTestCase {
|
|||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNextSendTime() throws Exception {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Database<Connection> db = open(false, new StoppedClock(now));
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Add a contact, a group and a message
|
||||||
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
|
true, true));
|
||||||
|
db.addGroup(txn, group);
|
||||||
|
db.addMessage(txn, message, UNKNOWN, false);
|
||||||
|
|
||||||
|
// There should be no messages to send
|
||||||
|
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
// Share the group with the contact - still no messages to send
|
||||||
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
|
db.addStatus(txn, contactId, messageId, false, false);
|
||||||
|
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
// Set the message's state to DELIVERED - still no messages to send
|
||||||
|
db.setMessageState(txn, messageId, DELIVERED);
|
||||||
|
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
// Share the message - now it should be sendable immediately
|
||||||
|
db.setMessageShared(txn, messageId);
|
||||||
|
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
// Update the message's expiry time as though we sent it - now the
|
||||||
|
// message should be sendable after one round-trip
|
||||||
|
db.updateExpiryTime(txn, contactId, messageId, 1000);
|
||||||
|
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
// Update the message's expiry time again - now it should be sendable
|
||||||
|
// after two round-trips
|
||||||
|
db.updateExpiryTime(txn, contactId, messageId, 1000);
|
||||||
|
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
// Delete the message - there should be no messages to send
|
||||||
|
db.deleteMessage(txn, messageId);
|
||||||
|
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExceptionHandling() throws Exception {
|
public void testExceptionHandling() throws Exception {
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
@@ -1652,10 +1700,15 @@ public class H2DatabaseTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Database<Connection> open(boolean resume) throws Exception {
|
private Database<Connection> open(boolean resume) throws Exception {
|
||||||
|
return open(resume, new SystemClock());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Database<Connection> open(boolean resume, Clock clock)
|
||||||
|
throws Exception {
|
||||||
Database<Connection> db = new H2Database(new TestDatabaseConfig(testDir,
|
Database<Connection> db = new H2Database(new TestDatabaseConfig(testDir,
|
||||||
MAX_SIZE), new SystemClock());
|
MAX_SIZE), clock);
|
||||||
if (!resume) TestUtils.deleteTestDirectory(testDir);
|
if (!resume) TestUtils.deleteTestDirectory(testDir);
|
||||||
db.open();
|
db.open(null);
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1683,4 +1736,23 @@ public class H2DatabaseTest extends BrambleTestCase {
|
|||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectory(testDir);
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class StoppedClock implements Clock {
|
||||||
|
|
||||||
|
private final long time;
|
||||||
|
|
||||||
|
private StoppedClock(long time) {
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long currentTimeMillis() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sleep(long milliseconds) throws InterruptedException {
|
||||||
|
Thread.sleep(milliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class H2MigrationTest extends DatabaseMigrationTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Database<Connection> createDatabase(List<Migration<Connection>> migrations)
|
||||||
|
throws Exception {
|
||||||
|
return new H2Database(config, clock) {
|
||||||
|
@Override
|
||||||
|
List<Migration<Connection>> getMigrations() {
|
||||||
|
return migrations;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertNotNull;
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||||
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class Migration30_31Test extends BrambleTestCase {
|
||||||
|
|
||||||
|
private static final String CREATE_GROUPS_STUB =
|
||||||
|
"CREATE TABLE groups"
|
||||||
|
+ " (groupID BINARY(32) NOT NULL,"
|
||||||
|
+ " PRIMARY KEY (groupId))";
|
||||||
|
|
||||||
|
private static final String CREATE_MESSAGES =
|
||||||
|
"CREATE TABLE messages"
|
||||||
|
+ " (messageId BINARY(32) NOT NULL,"
|
||||||
|
+ " groupId BINARY(32) NOT NULL,"
|
||||||
|
+ " timestamp BIGINT NOT NULL,"
|
||||||
|
+ " state INT NOT NULL,"
|
||||||
|
+ " shared BOOLEAN NOT NULL,"
|
||||||
|
+ " length INT NOT NULL,"
|
||||||
|
+ " raw BLOB," // Null if message has been deleted
|
||||||
|
+ " PRIMARY KEY (messageId),"
|
||||||
|
+ " FOREIGN KEY (groupId)"
|
||||||
|
+ " REFERENCES groups (groupId)"
|
||||||
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
|
private static final String CREATE_MESSAGE_METADATA_30 =
|
||||||
|
"CREATE TABLE messageMetadata"
|
||||||
|
+ " (messageId BINARY(32) NOT NULL,"
|
||||||
|
+ " key VARCHAR NOT NULL,"
|
||||||
|
+ " value BINARY NOT NULL,"
|
||||||
|
+ " PRIMARY KEY (messageId, key),"
|
||||||
|
+ " FOREIGN KEY (messageId)"
|
||||||
|
+ " REFERENCES messages (messageId)"
|
||||||
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
private final File db = new File(testDir, "db");
|
||||||
|
private final String url = "jdbc:h2:" + db.getAbsolutePath();
|
||||||
|
private final GroupId groupId = new GroupId(getRandomId());
|
||||||
|
private final GroupId groupId1 = new GroupId(getRandomId());
|
||||||
|
private final Message message = TestUtils.getMessage(groupId);
|
||||||
|
private final Message message1 = TestUtils.getMessage(groupId1);
|
||||||
|
private final Metadata meta = new Metadata(), meta1 = new Metadata();
|
||||||
|
|
||||||
|
private Connection connection = null;
|
||||||
|
|
||||||
|
public Migration30_31Test() {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
meta.put(getRandomString(123 + i), getRandomBytes(123 + i));
|
||||||
|
meta1.put(getRandomString(123 + i), getRandomBytes(123 + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
|
Class.forName("org.h2.Driver");
|
||||||
|
connection = DriverManager.getConnection(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
if (connection != null) connection.close();
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMigration() throws Exception {
|
||||||
|
try {
|
||||||
|
Statement s = connection.createStatement();
|
||||||
|
s.execute(CREATE_GROUPS_STUB);
|
||||||
|
s.execute(CREATE_MESSAGES);
|
||||||
|
s.execute(CREATE_MESSAGE_METADATA_30);
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
addGroup(groupId);
|
||||||
|
addMessage(message, DELIVERED, true);
|
||||||
|
addMessageMetadata30(message, meta);
|
||||||
|
assertMetadataEquals(meta, getMessageMetadata(message.getId()));
|
||||||
|
|
||||||
|
addGroup(groupId1);
|
||||||
|
addMessage(message1, UNKNOWN, false);
|
||||||
|
addMessageMetadata30(message1, meta1);
|
||||||
|
assertMetadataEquals(meta1, getMessageMetadata(message1.getId()));
|
||||||
|
|
||||||
|
new Migration30_31().migrate(connection);
|
||||||
|
|
||||||
|
assertMetadataEquals(meta, getMessageMetadata(message.getId()));
|
||||||
|
for (String key : meta.keySet()) {
|
||||||
|
GroupId g = getMessageMetadataGroupId31(message.getId(), key);
|
||||||
|
assertEquals(groupId, g);
|
||||||
|
State state = getMessageMetadataState31(message.getId(), key);
|
||||||
|
assertEquals(DELIVERED, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertMetadataEquals(meta1, getMessageMetadata(message1.getId()));
|
||||||
|
for (String key : meta1.keySet()) {
|
||||||
|
GroupId g = getMessageMetadataGroupId31(message1.getId(), key);
|
||||||
|
assertEquals(groupId1, g);
|
||||||
|
State state = getMessageMetadataState31(message1.getId(), key);
|
||||||
|
assertEquals(UNKNOWN, state);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addGroup(GroupId g) throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "INSERT INTO groups (groupId) VALUES (?)";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, g.getBytes());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMessage(Message m, State state, boolean shared)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
||||||
|
+ " state, shared, length, raw)"
|
||||||
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getId().getBytes());
|
||||||
|
ps.setBytes(2, m.getGroupId().getBytes());
|
||||||
|
ps.setLong(3, m.getTimestamp());
|
||||||
|
ps.setInt(4, state.getValue());
|
||||||
|
ps.setBoolean(5, shared);
|
||||||
|
byte[] raw = m.getRaw();
|
||||||
|
ps.setInt(6, raw.length);
|
||||||
|
ps.setBytes(7, raw);
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMessageMetadata30(Message m, Metadata meta)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "INSERT INTO messageMetadata"
|
||||||
|
+ " (messageId, key, value)"
|
||||||
|
+ " VALUES (?, ?, ?)";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getId().getBytes());
|
||||||
|
for (Entry<String, byte[]> e : meta.entrySet()) {
|
||||||
|
ps.setString(2, e.getKey());
|
||||||
|
ps.setBytes(3, e.getValue());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != meta.size())
|
||||||
|
throw new DbStateException();
|
||||||
|
for (int rows : batchAffected)
|
||||||
|
if (rows != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Metadata getMessageMetadata(MessageId m) throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT key, value FROM messageMetadata"
|
||||||
|
+ " WHERE messageId = ?";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
Metadata meta = new Metadata();
|
||||||
|
while (rs.next()) meta.put(rs.getString(1), rs.getBytes(2));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return meta;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (rs != null) rs.close();
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupId getMessageMetadataGroupId31(MessageId m, String key)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT groupId FROM messageMetadata"
|
||||||
|
+ " WHERE messageId = ? AND key = ?";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
ps.setString(2, key);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) throw new DbStateException();
|
||||||
|
GroupId g = new GroupId(rs.getBytes(1));
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return g;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (rs != null) rs.close();
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private State getMessageMetadataState31(MessageId m, String key)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT state FROM messageMetadata"
|
||||||
|
+ " WHERE messageId = ? AND key = ?";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
ps.setString(2, key);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) throw new DbStateException();
|
||||||
|
State state = State.fromValue(rs.getInt(1));
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return state;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (rs != null) rs.close();
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMetadataEquals(Metadata expected, Metadata actual) {
|
||||||
|
assertEquals(expected.size(), actual.size());
|
||||||
|
for (Entry<String, byte[]> e : expected.entrySet()) {
|
||||||
|
byte[] value = actual.get(e.getKey());
|
||||||
|
assertNotNull(value);
|
||||||
|
assertArrayEquals(e.getValue(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,14 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||||
import org.briarproject.bramble.test.RunAction;
|
import org.briarproject.bramble.test.RunAction;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.jmock.lib.legacy.ClassImposteriser;
|
import org.jmock.lib.legacy.ClassImposteriser;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -29,30 +29,37 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
public class PollerTest extends BrambleTestCase {
|
public class PollerTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
private final ConnectionManager connectionManager =
|
||||||
|
context.mock(ConnectionManager.class);
|
||||||
|
private final ConnectionRegistry connectionRegistry =
|
||||||
|
context.mock(ConnectionRegistry.class);
|
||||||
|
private final PluginManager pluginManager =
|
||||||
|
context.mock(PluginManager.class);
|
||||||
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final ScheduledFuture future = context.mock(ScheduledFuture.class);
|
||||||
|
private final SecureRandom random;
|
||||||
|
|
||||||
|
private final Executor ioExecutor = new ImmediateExecutor();
|
||||||
|
private final TransportId transportId = new TransportId("id");
|
||||||
private final ContactId contactId = new ContactId(234);
|
private final ContactId contactId = new ContactId(234);
|
||||||
private final int pollingInterval = 60 * 1000;
|
private final int pollingInterval = 60 * 1000;
|
||||||
private final long now = System.currentTimeMillis();
|
private final long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
public PollerTest() {
|
||||||
|
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||||
|
random = context.mock(SecureRandom.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConnectOnContactStatusChanged() throws Exception {
|
public void testConnectOnContactStatusChanged() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
|
||||||
ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
ConnectionManager connectionManager =
|
|
||||||
context.mock(ConnectionManager.class);
|
|
||||||
ConnectionRegistry connectionRegistry =
|
|
||||||
context.mock(ConnectionRegistry.class);
|
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
// Two simplex plugins: one supports polling, the other doesn't
|
// Two simplex plugins: one supports polling, the other doesn't
|
||||||
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||||
SimplexPlugin simplexPlugin1 =
|
SimplexPlugin simplexPlugin1 =
|
||||||
@@ -120,28 +127,12 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
connectionRegistry, pluginManager, random, clock);
|
connectionRegistry, pluginManager, random, clock);
|
||||||
|
|
||||||
p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
|
p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRescheduleAndReconnectOnConnectionClosed()
|
public void testRescheduleAndReconnectOnConnectionClosed()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
|
||||||
ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
ConnectionManager connectionManager =
|
|
||||||
context.mock(ConnectionManager.class);
|
|
||||||
ConnectionRegistry connectionRegistry =
|
|
||||||
context.mock(ConnectionRegistry.class);
|
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
||||||
TransportId transportId = new TransportId("id");
|
|
||||||
DuplexTransportConnection duplexConnection =
|
DuplexTransportConnection duplexConnection =
|
||||||
context.mock(DuplexTransportConnection.class);
|
context.mock(DuplexTransportConnection.class);
|
||||||
|
|
||||||
@@ -168,6 +159,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with((long) pollingInterval), with(MILLISECONDS));
|
with((long) pollingInterval), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
// connectToContact()
|
// connectToContact()
|
||||||
// Check whether the contact is already connected
|
// Check whether the contact is already connected
|
||||||
oneOf(connectionRegistry).isConnected(contactId, transportId);
|
oneOf(connectionRegistry).isConnected(contactId, transportId);
|
||||||
@@ -185,28 +177,12 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
|
|
||||||
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
|
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
|
||||||
false));
|
false));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRescheduleOnConnectionOpened() throws Exception {
|
public void testRescheduleOnConnectionOpened() throws Exception {
|
||||||
Mockery context = new Mockery();
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
|
||||||
ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
ConnectionManager connectionManager =
|
|
||||||
context.mock(ConnectionManager.class);
|
|
||||||
ConnectionRegistry connectionRegistry =
|
|
||||||
context.mock(ConnectionRegistry.class);
|
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
|
||||||
TransportId transportId = new TransportId("id");
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
allowing(plugin).getId();
|
allowing(plugin).getId();
|
||||||
@@ -224,6 +200,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with((long) pollingInterval), with(MILLISECONDS));
|
with((long) pollingInterval), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||||
@@ -231,27 +208,11 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
|
|
||||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||||
false));
|
false));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
|
public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
|
||||||
Mockery context = new Mockery();
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
|
||||||
ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
ConnectionManager connectionManager =
|
|
||||||
context.mock(ConnectionManager.class);
|
|
||||||
ConnectionRegistry connectionRegistry =
|
|
||||||
context.mock(ConnectionRegistry.class);
|
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
|
||||||
TransportId transportId = new TransportId("id");
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
allowing(plugin).getId();
|
allowing(plugin).getId();
|
||||||
@@ -270,6 +231,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with((long) pollingInterval), with(MILLISECONDS));
|
with((long) pollingInterval), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
// Second event
|
// Second event
|
||||||
// Get the plugin
|
// Get the plugin
|
||||||
oneOf(pluginManager).getPlugin(transportId);
|
oneOf(pluginManager).getPlugin(transportId);
|
||||||
@@ -291,27 +253,59 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
false));
|
false));
|
||||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||||
false));
|
false));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPollOnTransportEnabled() throws Exception {
|
public void testRescheduleReplacesLaterTask() throws Exception {
|
||||||
Mockery context = new Mockery();
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
context.checking(new Expectations() {{
|
||||||
ScheduledExecutorService scheduler =
|
allowing(plugin).getId();
|
||||||
context.mock(ScheduledExecutorService.class);
|
will(returnValue(transportId));
|
||||||
ConnectionManager connectionManager =
|
// First event
|
||||||
context.mock(ConnectionManager.class);
|
// Get the plugin
|
||||||
ConnectionRegistry connectionRegistry =
|
oneOf(pluginManager).getPlugin(transportId);
|
||||||
context.mock(ConnectionRegistry.class);
|
will(returnValue(plugin));
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
// The plugin supports polling
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
oneOf(plugin).shouldPoll();
|
||||||
Clock clock = context.mock(Clock.class);
|
will(returnValue(true));
|
||||||
|
// Schedule the next poll
|
||||||
|
oneOf(plugin).getPollingInterval();
|
||||||
|
will(returnValue(pollingInterval));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
|
with((long) pollingInterval), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
|
// Second event
|
||||||
|
// Get the plugin
|
||||||
|
oneOf(pluginManager).getPlugin(transportId);
|
||||||
|
will(returnValue(plugin));
|
||||||
|
// The plugin supports polling
|
||||||
|
oneOf(plugin).shouldPoll();
|
||||||
|
will(returnValue(true));
|
||||||
|
// Replace the previously scheduled task, due later
|
||||||
|
oneOf(plugin).getPollingInterval();
|
||||||
|
will(returnValue(pollingInterval - 2));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now + 1));
|
||||||
|
oneOf(future).cancel(false);
|
||||||
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
|
with((long) pollingInterval - 2), with(MILLISECONDS));
|
||||||
|
}});
|
||||||
|
|
||||||
|
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||||
|
connectionRegistry, pluginManager, random, clock);
|
||||||
|
|
||||||
|
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||||
|
false));
|
||||||
|
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||||
|
false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPollsOnTransportEnabled() throws Exception {
|
||||||
Plugin plugin = context.mock(Plugin.class);
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
TransportId transportId = new TransportId("id");
|
|
||||||
List<ContactId> connected = Collections.singletonList(contactId);
|
List<ContactId> connected = Collections.singletonList(contactId);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -328,6 +322,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
||||||
with(MILLISECONDS));
|
with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
will(new RunAction());
|
will(new RunAction());
|
||||||
// Running the polling task schedules the next polling task
|
// Running the polling task schedules the next polling task
|
||||||
oneOf(plugin).getPollingInterval();
|
oneOf(plugin).getPollingInterval();
|
||||||
@@ -338,6 +333,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
|
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
// Poll the plugin
|
// Poll the plugin
|
||||||
oneOf(connectionRegistry).getConnectedContacts(transportId);
|
oneOf(connectionRegistry).getConnectedContacts(transportId);
|
||||||
will(returnValue(connected));
|
will(returnValue(connected));
|
||||||
@@ -348,7 +344,36 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
connectionRegistry, pluginManager, random, clock);
|
connectionRegistry, pluginManager, random, clock);
|
||||||
|
|
||||||
p.eventOccurred(new TransportEnabledEvent(transportId));
|
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||||
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
@Test
|
||||||
|
public void testCancelsPollingOnTransportDisabled() throws Exception {
|
||||||
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
|
List<ContactId> connected = Collections.singletonList(contactId);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
allowing(plugin).getId();
|
||||||
|
will(returnValue(transportId));
|
||||||
|
// Get the plugin
|
||||||
|
oneOf(pluginManager).getPlugin(transportId);
|
||||||
|
will(returnValue(plugin));
|
||||||
|
// The plugin supports polling
|
||||||
|
oneOf(plugin).shouldPoll();
|
||||||
|
will(returnValue(true));
|
||||||
|
// Schedule a polling task immediately
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
||||||
|
with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
|
// The plugin is disabled before the task runs - cancel the task
|
||||||
|
oneOf(future).cancel(false);
|
||||||
|
}});
|
||||||
|
|
||||||
|
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||||
|
connectionRegistry, pluginManager, random, clock);
|
||||||
|
|
||||||
|
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||||
|
p.eventOccurred(new TransportDisabledEvent(transportId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.tcp;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
@@ -11,6 +10,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.plugin.tcp.LanTcpPlugin.LanAddressComparator;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -22,13 +22,12 @@ import java.net.NetworkInterface;
|
|||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
@@ -56,6 +55,9 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
// Local and remote in 192.168.0.0/16 should return true
|
// Local and remote in 192.168.0.0/16 should return true
|
||||||
assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
|
assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
|
||||||
makeAddress(192, 168, 255, 255)));
|
makeAddress(192, 168, 255, 255)));
|
||||||
|
// Local and remote in 169.254.0.0/16 (link-local) should return true
|
||||||
|
assertTrue(plugin.addressesAreOnSameLan(makeAddress(169, 254, 0, 0),
|
||||||
|
makeAddress(169, 254, 255, 255)));
|
||||||
// Local and remote in different recognised prefixes should return false
|
// Local and remote in different recognised prefixes should return false
|
||||||
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
|
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
|
||||||
makeAddress(172, 31, 255, 255)));
|
makeAddress(172, 31, 255, 255)));
|
||||||
@@ -193,9 +195,16 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
KeyAgreementListener kal =
|
KeyAgreementListener kal =
|
||||||
plugin.createKeyAgreementListener(new byte[COMMIT_LENGTH]);
|
plugin.createKeyAgreementListener(new byte[COMMIT_LENGTH]);
|
||||||
assertNotNull(kal);
|
assertNotNull(kal);
|
||||||
Callable<KeyAgreementConnection> c = kal.listen();
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
FutureTask<KeyAgreementConnection> f = new FutureTask<>(c);
|
AtomicBoolean error = new AtomicBoolean(false);
|
||||||
new Thread(f).start();
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
kal.accept();
|
||||||
|
latch.countDown();
|
||||||
|
} catch (IOException e) {
|
||||||
|
error.set(true);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
// The plugin should have bound a socket and stored the port number
|
// The plugin should have bound a socket and stored the port number
|
||||||
BdfList descriptor = kal.getDescriptor();
|
BdfList descriptor = kal.getDescriptor();
|
||||||
assertEquals(3, descriptor.size());
|
assertEquals(3, descriptor.size());
|
||||||
@@ -211,10 +220,12 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
InetSocketAddress socketAddr = new InetSocketAddress(addr, port);
|
InetSocketAddress socketAddr = new InetSocketAddress(addr, port);
|
||||||
Socket s = new Socket();
|
Socket s = new Socket();
|
||||||
s.connect(socketAddr, 100);
|
s.connect(socketAddr, 100);
|
||||||
assertNotNull(f.get(5, SECONDS));
|
// Check that the connection was accepted
|
||||||
|
assertTrue(latch.await(5, SECONDS));
|
||||||
|
assertFalse(error.get());
|
||||||
|
// Clean up
|
||||||
s.close();
|
s.close();
|
||||||
kal.close();
|
kal.close();
|
||||||
// Stop the plugin
|
|
||||||
plugin.stop();
|
plugin.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +274,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
descriptor.add(local.getPort());
|
descriptor.add(local.getPort());
|
||||||
// Connect to the port
|
// Connect to the port
|
||||||
DuplexTransportConnection d = plugin.createKeyAgreementConnection(
|
DuplexTransportConnection d = plugin.createKeyAgreementConnection(
|
||||||
new byte[COMMIT_LENGTH], descriptor, 5000);
|
new byte[COMMIT_LENGTH], descriptor);
|
||||||
assertNotNull(d);
|
assertNotNull(d);
|
||||||
// Check that the connection was accepted
|
// Check that the connection was accepted
|
||||||
assertTrue(latch.await(5, SECONDS));
|
assertTrue(latch.await(5, SECONDS));
|
||||||
@@ -275,6 +286,57 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
plugin.stop();
|
plugin.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorPrefersNonZeroPorts() throws Exception {
|
||||||
|
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||||
|
InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234);
|
||||||
|
InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0);
|
||||||
|
|
||||||
|
assertEquals(0, comparator.compare(nonZero, nonZero));
|
||||||
|
assertTrue(comparator.compare(nonZero, zero) < 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(zero, nonZero) > 0);
|
||||||
|
assertEquals(0, comparator.compare(zero, zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorPrefersLongerPrefixes() throws Exception {
|
||||||
|
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||||
|
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
|
||||||
|
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
|
||||||
|
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
|
||||||
|
|
||||||
|
assertEquals(0, comparator.compare(prefix192, prefix192));
|
||||||
|
assertTrue(comparator.compare(prefix192, prefix172) < 0);
|
||||||
|
assertTrue(comparator.compare(prefix192, prefix10) < 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(prefix172, prefix192) > 0);
|
||||||
|
assertEquals(0, comparator.compare(prefix172, prefix172));
|
||||||
|
assertTrue(comparator.compare(prefix172, prefix10) < 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(prefix10, prefix192) > 0);
|
||||||
|
assertTrue(comparator.compare(prefix10, prefix172) > 0);
|
||||||
|
assertEquals(0, comparator.compare(prefix10, prefix10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorPrefersSiteLocalToLinkLocal() throws Exception {
|
||||||
|
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||||
|
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
|
||||||
|
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
|
||||||
|
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
|
||||||
|
InetSocketAddress linkLocal = new InetSocketAddress("169.254.0.1", 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(prefix192, linkLocal) < 0);
|
||||||
|
assertTrue(comparator.compare(prefix172, linkLocal) < 0);
|
||||||
|
assertTrue(comparator.compare(prefix10, linkLocal) < 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(linkLocal, prefix192) > 0);
|
||||||
|
assertTrue(comparator.compare(linkLocal, prefix172) > 0);
|
||||||
|
assertTrue(comparator.compare(linkLocal, prefix10) > 0);
|
||||||
|
assertEquals(0, comparator.compare(linkLocal, linkLocal));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean systemHasLocalIpv4Address() throws Exception {
|
private boolean systemHasLocalIpv4Address() throws Exception {
|
||||||
for (NetworkInterface i : Collections.list(
|
for (NetworkInterface i : Collections.list(
|
||||||
NetworkInterface.getNetworkInterfaces())) {
|
NetworkInterface.getNetworkInterfaces())) {
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
|
Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
|
will(returnValue(false));
|
||||||
oneOf(db).addGroup(txn, localGroup);
|
oneOf(db).addGroup(txn, localGroup);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(contacts));
|
will(returnValue(contacts));
|
||||||
@@ -123,7 +125,21 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatesGroupWhenAddingContact() throws Exception {
|
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
|
||||||
|
throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
|
will(returnValue(true));
|
||||||
|
}});
|
||||||
|
|
||||||
|
TransportPropertyManagerImpl t = createInstance();
|
||||||
|
t.createLocalState(txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatesContactGroupWhenAddingContact() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact(true);
|
Contact contact = getContact(true);
|
||||||
Group contactGroup = getGroup();
|
Group contactGroup = getGroup();
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import javax.inject.Singleton;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class TestLifecycleModule {
|
public class TestLifecycleModule {
|
||||||
|
|
||||||
@@ -57,6 +59,11 @@ public class TestLifecycleModule {
|
|||||||
@Override
|
@Override
|
||||||
public void waitForShutdown() throws InterruptedException {
|
public void waitForShutdown() throws InterruptedException {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LifecycleState getLifecycleState() {
|
||||||
|
return RUNNING;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return lifecycleManager;
|
return lifecycleManager;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin;
|
package org.briarproject.bramble.plugin;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -8,7 +9,7 @@ import org.briarproject.bramble.api.plugin.PluginConfig;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
|
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
|
||||||
import org.briarproject.bramble.plugin.bluetooth.BluetoothPluginFactory;
|
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory;
|
import org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory;
|
||||||
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
||||||
@@ -30,9 +31,10 @@ public class DesktopPluginModule extends PluginModule {
|
|||||||
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
|
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
|
||||||
SecureRandom random, BackoffFactory backoffFactory,
|
SecureRandom random, BackoffFactory backoffFactory,
|
||||||
ReliabilityLayerFactory reliabilityFactory,
|
ReliabilityLayerFactory reliabilityFactory,
|
||||||
ShutdownManager shutdownManager) {
|
ShutdownManager shutdownManager, EventBus eventBus) {
|
||||||
DuplexPluginFactory bluetooth = new BluetoothPluginFactory(ioExecutor,
|
DuplexPluginFactory bluetooth =
|
||||||
random, backoffFactory);
|
new JavaBluetoothPluginFactory(ioExecutor, random, eventBus,
|
||||||
|
backoffFactory);
|
||||||
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
|
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
|
||||||
reliabilityFactory);
|
reliabilityFactory);
|
||||||
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,
|
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package org.briarproject.bramble.plugin.bluetooth;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.bluetooth.BluetoothStateException;
|
||||||
|
import javax.bluetooth.LocalDevice;
|
||||||
|
import javax.microedition.io.Connector;
|
||||||
|
import javax.microedition.io.StreamConnection;
|
||||||
|
import javax.microedition.io.StreamConnectionNotifier;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.isValidMac;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(JavaBluetoothPlugin.class.getName());
|
||||||
|
|
||||||
|
// Non-null if the plugin started successfully
|
||||||
|
private volatile LocalDevice localDevice = null;
|
||||||
|
|
||||||
|
JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||||
|
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||||
|
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void initialiseAdapter() throws IOException {
|
||||||
|
try {
|
||||||
|
localDevice = LocalDevice.getLocalDevice();
|
||||||
|
} catch (UnsatisfiedLinkError | BluetoothStateException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isAdapterEnabled() {
|
||||||
|
return localDevice != null && LocalDevice.isPowerOn();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void enableAdapter() {
|
||||||
|
// Nothing we can do on this platform
|
||||||
|
LOG.info("Could not enable Bluetooth");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void disableAdapterIfEnabledByUs() {
|
||||||
|
// We didn't enable it so we don't need to disable it
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setEnabledByUs() {
|
||||||
|
// Irrelevant on this platform
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
String getBluetoothAddress() {
|
||||||
|
return localDevice.getBluetoothAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
StreamConnectionNotifier openServerSocket(String uuid) throws IOException {
|
||||||
|
String url = makeUrl("localhost", uuid);
|
||||||
|
return (StreamConnectionNotifier) Connector.open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void tryToClose(@Nullable StreamConnectionNotifier ss) {
|
||||||
|
try {
|
||||||
|
if (ss != null) ss.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DuplexTransportConnection acceptConnection(StreamConnectionNotifier ss)
|
||||||
|
throws IOException {
|
||||||
|
return wrapSocket(ss.acceptAndOpen());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isValidAddress(String address) {
|
||||||
|
return isValidMac(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DuplexTransportConnection connectTo(String address, String uuid)
|
||||||
|
throws IOException {
|
||||||
|
String url = makeUrl(address, uuid);
|
||||||
|
return wrapSocket((StreamConnection) Connector.open(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeUrl(String address, String uuid) {
|
||||||
|
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||||
|
}
|
||||||
|
|
||||||
|
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||||
|
return new JavaBluetoothTransportConnection(this, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin.bluetooth;
|
package org.briarproject.bramble.plugin.bluetooth;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||||
@@ -17,7 +18,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class BluetoothPluginFactory implements DuplexPluginFactory {
|
public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||||
|
|
||||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||||
@@ -27,12 +28,15 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
|
||||||
public BluetoothPluginFactory(Executor ioExecutor,
|
public JavaBluetoothPluginFactory(Executor ioExecutor,
|
||||||
SecureRandom secureRandom, BackoffFactory backoffFactory) {
|
SecureRandom secureRandom, EventBus eventBus,
|
||||||
|
BackoffFactory backoffFactory) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
this.backoffFactory = backoffFactory;
|
this.backoffFactory = backoffFactory;
|
||||||
|
this.eventBus = eventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,7 +53,9 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
return new BluetoothPlugin(ioExecutor, secureRandom, backoff, callback,
|
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(ioExecutor,
|
||||||
MAX_LATENCY);
|
secureRandom, backoff, callback, MAX_LATENCY);
|
||||||
|
eventBus.addListener(plugin);
|
||||||
|
return plugin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,12 @@ import java.io.OutputStream;
|
|||||||
import javax.microedition.io.StreamConnection;
|
import javax.microedition.io.StreamConnection;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class BluetoothTransportConnection extends AbstractDuplexTransportConnection {
|
class JavaBluetoothTransportConnection
|
||||||
|
extends AbstractDuplexTransportConnection {
|
||||||
|
|
||||||
private final StreamConnection stream;
|
private final StreamConnection stream;
|
||||||
|
|
||||||
BluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
lang_map = pt_BR: pt-rBR, fr_FR: fr, nb_NO: nb, zh-Hans: zh-rCN
|
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN
|
||||||
|
|
||||||
[briar.stringsxml-5]
|
[briar.stringsxml-5]
|
||||||
file_filter = src/main/res/values-<lang>/strings.xml
|
file_filter = src/main/res/values-<lang>/strings.xml
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
|
|
||||||
|
configurations.all {
|
||||||
|
resolutionStrategy.preferProjectModules()
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':briar-core', configuration: 'default')
|
implementation project(path: ':briar-core', configuration: 'default')
|
||||||
implementation project(path: ':bramble-core', configuration: 'default')
|
implementation project(path: ':bramble-core', configuration: 'default')
|
||||||
@@ -20,7 +24,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||||
implementation "com.android.support:support-annotations:$supportVersion"
|
implementation "com.android.support:support-annotations:$supportVersion"
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta3'
|
||||||
|
|
||||||
implementation('ch.acra:acra:4.8.5') {
|
implementation('ch.acra:acra:4.8.5') {
|
||||||
exclude module: 'support-v4'
|
exclude module: 'support-v4'
|
||||||
@@ -61,8 +65,8 @@ dependencyVerification {
|
|||||||
'ch.acra:acra:4.8.5:acra-4.8.5.aar:afd5b28934d5166b55f261c85685ad59e8a4ebe9ca1960906afaa8c76d8dc9eb',
|
'ch.acra:acra:4.8.5:acra-4.8.5.aar:afd5b28934d5166b55f261c85685ad59e8a4ebe9ca1960906afaa8c76d8dc9eb',
|
||||||
'classworlds:classworlds:1.1-alpha-2:classworlds-1.1-alpha-2.jar:2bf4e59f3acd106fea6145a9a88fe8956509f8b9c0fdd11eb96fee757269e3f3',
|
'classworlds:classworlds:1.1-alpha-2:classworlds-1.1-alpha-2.jar:2bf4e59f3acd106fea6145a9a88fe8956509f8b9c0fdd11eb96fee757269e3f3',
|
||||||
'com.almworks.sqlite4java:sqlite4java:0.282:sqlite4java-0.282.jar:9e1d8dd83ca6003f841e3af878ce2dc7c22497493a7bb6d1b62ec1b0d0a83c05',
|
'com.almworks.sqlite4java:sqlite4java:0.282:sqlite4java-0.282.jar:9e1d8dd83ca6003f841e3af878ce2dc7c22497493a7bb6d1b62ec1b0d0a83c05',
|
||||||
'com.android.support.constraint:constraint-layout-solver:1.0.2:constraint-layout-solver-1.0.2.jar:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d',
|
'com.android.support.constraint:constraint-layout-solver:1.1.0-beta3:constraint-layout-solver-1.1.0-beta3.jar:c9084108415046c423983bdff8cf04c8e9a5bed41b8d5329f3764c08312ee3dd',
|
||||||
'com.android.support.constraint:constraint-layout:1.0.2:constraint-layout-1.0.2.aar:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085',
|
'com.android.support.constraint:constraint-layout:1.1.0-beta3:constraint-layout-1.1.0-beta3.aar:1754a6bd135feae485aa2ebf9e170f0f3d3282b392f8aa3067d0ed668839db79',
|
||||||
'com.android.support:animated-vector-drawable:27.0.1:animated-vector-drawable-27.0.1.aar:365050110411c86c7eec86101b49ab53557ffe6667f60b19055f1d35c38a577b',
|
'com.android.support:animated-vector-drawable:27.0.1:animated-vector-drawable-27.0.1.aar:365050110411c86c7eec86101b49ab53557ffe6667f60b19055f1d35c38a577b',
|
||||||
'com.android.support:appcompat-v7:27.0.1:appcompat-v7-27.0.1.aar:1402c29a49db30346c21a7d40634461765b3ab826f5dd95bc4dcc76787b21851',
|
'com.android.support:appcompat-v7:27.0.1:appcompat-v7-27.0.1.aar:1402c29a49db30346c21a7d40634461765b3ab826f5dd95bc4dcc76787b21851',
|
||||||
'com.android.support:cardview-v7:27.0.1:cardview-v7-27.0.1.aar:43fccd44086c51eaa9d78be2fcf0dfea1556c8876a6fd325ea8d24e860054202',
|
'com.android.support:cardview-v7:27.0.1:cardview-v7-27.0.1.aar:43fccd44086c51eaa9d78be2fcf0dfea1556c8876a6fd325ea8d24e860054202',
|
||||||
@@ -169,7 +173,7 @@ def getGitHash = { ->
|
|||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
try {
|
try {
|
||||||
exec {
|
exec {
|
||||||
commandLine 'git', 'rev-parse', '--short', 'HEAD'
|
commandLine 'git', 'rev-parse', '--short=7', 'HEAD'
|
||||||
standardOutput = stdout
|
standardOutput = stdout
|
||||||
}
|
}
|
||||||
return stdout.toString().trim()
|
return stdout.toString().trim()
|
||||||
@@ -185,8 +189,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 1612
|
versionCode 1619
|
||||||
versionName "0.16.12"
|
versionName "0.16.19"
|
||||||
applicationId "org.briarproject.briar.beta"
|
applicationId "org.briarproject.briar.beta"
|
||||||
resValue "string", "app_package", "org.briarproject.briar.beta"
|
resValue "string", "app_package", "org.briarproject.briar.beta"
|
||||||
resValue "string", "app_name", "Briar Beta"
|
resValue "string", "app_name", "Briar Beta"
|
||||||
@@ -197,7 +201,7 @@ android {
|
|||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
resValue "string", "app_package", "org.briarproject.briar.beta.debug"
|
resValue "string", "app_package", "org.briarproject.briar.beta.debug"
|
||||||
resValue "string", "app_name", "Briar Debug"
|
resValue "string", "app_name", "Briar Beta Debug"
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
|
|||||||
@@ -77,6 +77,11 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".android.login.OpenDatabaseActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleTop"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||||
android:theme="@style/BriarTheme.NoActionBar"
|
android:theme="@style/BriarTheme.NoActionBar"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import org.briarproject.bramble.BrambleAndroidModule;
|
import org.briarproject.bramble.BrambleAndroidModule;
|
||||||
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||||
import org.briarproject.bramble.BrambleCoreModule;
|
import org.briarproject.bramble.BrambleCoreModule;
|
||||||
@@ -89,6 +91,8 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
AndroidNotificationManager androidNotificationManager();
|
AndroidNotificationManager androidNotificationManager();
|
||||||
|
|
||||||
|
SharedPreferences sharedPreferences();
|
||||||
|
|
||||||
ScreenFilterMonitor screenFilterMonitor();
|
ScreenFilterMonitor screenFilterMonitor();
|
||||||
|
|
||||||
ConnectionRegistry connectionRegistry();
|
ConnectionRegistry connectionRegistry();
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
@@ -44,6 +48,7 @@ import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
@@ -57,8 +62,10 @@ import javax.inject.Inject;
|
|||||||
import static android.app.Notification.DEFAULT_LIGHTS;
|
import static android.app.Notification.DEFAULT_LIGHTS;
|
||||||
import static android.app.Notification.DEFAULT_SOUND;
|
import static android.app.Notification.DEFAULT_SOUND;
|
||||||
import static android.app.Notification.DEFAULT_VIBRATE;
|
import static android.app.Notification.DEFAULT_VIBRATE;
|
||||||
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
|
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
|
||||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
|
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
@@ -83,6 +90,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
private static final int BLOG_POST_NOTIFICATION_ID = 6;
|
private static final int BLOG_POST_NOTIFICATION_ID = 6;
|
||||||
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7;
|
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7;
|
||||||
|
|
||||||
|
// Channel IDs
|
||||||
|
private static final String CONTACT_CHANNEL_ID = "contacts";
|
||||||
|
private static final String GROUP_CHANNEL_ID = "groups";
|
||||||
|
private static final String FORUM_CHANNEL_ID = "forums";
|
||||||
|
private static final String BLOG_CHANNEL_ID = "blogs";
|
||||||
|
|
||||||
private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2);
|
private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2);
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -91,8 +104,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final SettingsManager settingsManager;
|
private final SettingsManager settingsManager;
|
||||||
private final AndroidExecutor androidExecutor;
|
private final AndroidExecutor androidExecutor;
|
||||||
private final Context appContext;
|
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
private final Context appContext;
|
||||||
|
private final NotificationManager notificationManager;
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
// The following must only be accessed on the main UI thread
|
// The following must only be accessed on the main UI thread
|
||||||
@@ -121,6 +135,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
appContext = app.getApplicationContext();
|
appContext = app.getApplicationContext();
|
||||||
|
notificationManager = (NotificationManager)
|
||||||
|
appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,6 +148,37 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
throw new ServiceException(e);
|
throw new ServiceException(e);
|
||||||
}
|
}
|
||||||
|
if (SDK_INT >= 26) {
|
||||||
|
// Create notification channels
|
||||||
|
Callable<Void> task = () -> {
|
||||||
|
createNotificationChannel(CONTACT_CHANNEL_ID,
|
||||||
|
R.string.contact_list_button);
|
||||||
|
createNotificationChannel(GROUP_CHANNEL_ID,
|
||||||
|
R.string.groups_button);
|
||||||
|
createNotificationChannel(FORUM_CHANNEL_ID,
|
||||||
|
R.string.forums_button);
|
||||||
|
createNotificationChannel(BLOG_CHANNEL_ID,
|
||||||
|
R.string.blogs_button);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
androidExecutor.runOnUiThread(task).get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
throw new ServiceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(26)
|
||||||
|
private void createNotificationChannel(String channelId,
|
||||||
|
@StringRes int name) {
|
||||||
|
NotificationChannel nc =
|
||||||
|
new NotificationChannel(channelId, appContext.getString(name),
|
||||||
|
IMPORTANCE_DEFAULT);
|
||||||
|
nc.enableLights(true);
|
||||||
|
nc.setLightColor(
|
||||||
|
ContextCompat.getColor(appContext, R.color.briar_green_light));
|
||||||
|
notificationManager.createNotificationChannel(nc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -156,44 +203,34 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
private void clearContactNotification() {
|
private void clearContactNotification() {
|
||||||
contactCounts.clear();
|
contactCounts.clear();
|
||||||
contactTotal = 0;
|
contactTotal = 0;
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
|
||||||
NotificationManager nm = (NotificationManager) o;
|
|
||||||
nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void clearGroupMessageNotification() {
|
private void clearGroupMessageNotification() {
|
||||||
groupCounts.clear();
|
groupCounts.clear();
|
||||||
groupTotal = 0;
|
groupTotal = 0;
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.cancel(GROUP_MESSAGE_NOTIFICATION_ID);
|
||||||
NotificationManager nm = (NotificationManager) o;
|
|
||||||
nm.cancel(GROUP_MESSAGE_NOTIFICATION_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void clearForumPostNotification() {
|
private void clearForumPostNotification() {
|
||||||
forumCounts.clear();
|
forumCounts.clear();
|
||||||
forumTotal = 0;
|
forumTotal = 0;
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.cancel(FORUM_POST_NOTIFICATION_ID);
|
||||||
NotificationManager nm = (NotificationManager) o;
|
|
||||||
nm.cancel(FORUM_POST_NOTIFICATION_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void clearBlogPostNotification() {
|
private void clearBlogPostNotification() {
|
||||||
blogCounts.clear();
|
blogCounts.clear();
|
||||||
blogTotal = 0;
|
blogTotal = 0;
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.cancel(BLOG_POST_NOTIFICATION_ID);
|
||||||
NotificationManager nm = (NotificationManager) o;
|
|
||||||
nm.cancel(BLOG_POST_NOTIFICATION_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void clearIntroductionSuccessNotification() {
|
private void clearIntroductionSuccessNotification() {
|
||||||
introductionTotal = 0;
|
introductionTotal = 0;
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.cancel(INTRODUCTION_SUCCESS_NOTIFICATION_ID);
|
||||||
NotificationManager nm = (NotificationManager) o;
|
|
||||||
nm.cancel(INTRODUCTION_SUCCESS_NOTIFICATION_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -269,8 +306,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
if (contactTotal == 0) {
|
if (contactTotal == 0) {
|
||||||
clearContactNotification();
|
clearContactNotification();
|
||||||
} else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) {
|
} else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) {
|
||||||
BriarNotificationBuilder b =
|
BriarNotificationBuilder b = new BriarNotificationBuilder(
|
||||||
new BriarNotificationBuilder(appContext);
|
appContext, CONTACT_CHANNEL_ID);
|
||||||
b.setSmallIcon(R.drawable.notification_private_message);
|
b.setSmallIcon(R.drawable.notification_private_message);
|
||||||
b.setColorRes(R.color.briar_primary);
|
b.setColorRes(R.color.briar_primary);
|
||||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||||
@@ -305,9 +342,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
t.addNextIntent(i);
|
t.addNextIntent(i);
|
||||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||||
}
|
}
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.notify(PRIVATE_MESSAGE_NOTIFICATION_ID,
|
||||||
NotificationManager nm = (NotificationManager) o;
|
b.build());
|
||||||
nm.notify(PRIVATE_MESSAGE_NOTIFICATION_ID, b.build());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +414,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
clearGroupMessageNotification();
|
clearGroupMessageNotification();
|
||||||
} else if (settings.getBoolean(PREF_NOTIFY_GROUP, true)) {
|
} else if (settings.getBoolean(PREF_NOTIFY_GROUP, true)) {
|
||||||
BriarNotificationBuilder b =
|
BriarNotificationBuilder b =
|
||||||
new BriarNotificationBuilder(appContext);
|
new BriarNotificationBuilder(appContext, GROUP_CHANNEL_ID);
|
||||||
b.setSmallIcon(R.drawable.notification_private_group);
|
b.setSmallIcon(R.drawable.notification_private_group);
|
||||||
b.setColorRes(R.color.briar_primary);
|
b.setColorRes(R.color.briar_primary);
|
||||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||||
@@ -414,9 +450,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
t.addNextIntent(i);
|
t.addNextIntent(i);
|
||||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||||
}
|
}
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.notify(GROUP_MESSAGE_NOTIFICATION_ID,
|
||||||
NotificationManager nm = (NotificationManager) o;
|
b.build());
|
||||||
nm.notify(GROUP_MESSAGE_NOTIFICATION_ID, b.build());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +490,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
clearForumPostNotification();
|
clearForumPostNotification();
|
||||||
} else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) {
|
} else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) {
|
||||||
BriarNotificationBuilder b =
|
BriarNotificationBuilder b =
|
||||||
new BriarNotificationBuilder(appContext);
|
new BriarNotificationBuilder(appContext, FORUM_CHANNEL_ID);
|
||||||
b.setSmallIcon(R.drawable.notification_forum);
|
b.setSmallIcon(R.drawable.notification_forum);
|
||||||
b.setColorRes(R.color.briar_primary);
|
b.setColorRes(R.color.briar_primary);
|
||||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||||
@@ -491,9 +526,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
t.addNextIntent(i);
|
t.addNextIntent(i);
|
||||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||||
}
|
}
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.notify(FORUM_POST_NOTIFICATION_ID, b.build());
|
||||||
NotificationManager nm = (NotificationManager) o;
|
|
||||||
nm.notify(FORUM_POST_NOTIFICATION_ID, b.build());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +565,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
clearBlogPostNotification();
|
clearBlogPostNotification();
|
||||||
} else if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {
|
} else if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {
|
||||||
BriarNotificationBuilder b =
|
BriarNotificationBuilder b =
|
||||||
new BriarNotificationBuilder(appContext);
|
new BriarNotificationBuilder(appContext, BLOG_CHANNEL_ID);
|
||||||
b.setSmallIcon(R.drawable.notification_blog);
|
b.setSmallIcon(R.drawable.notification_blog);
|
||||||
b.setColorRes(R.color.briar_primary);
|
b.setColorRes(R.color.briar_primary);
|
||||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||||
@@ -555,9 +588,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
t.addNextIntent(i);
|
t.addNextIntent(i);
|
||||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||||
|
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.notify(BLOG_POST_NOTIFICATION_ID, b.build());
|
||||||
NotificationManager nm = (NotificationManager) o;
|
|
||||||
nm.notify(BLOG_POST_NOTIFICATION_ID, b.build());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +608,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void updateIntroductionNotification() {
|
private void updateIntroductionNotification() {
|
||||||
BriarNotificationBuilder b = new BriarNotificationBuilder(appContext);
|
BriarNotificationBuilder b =
|
||||||
|
new BriarNotificationBuilder(appContext, CONTACT_CHANNEL_ID);
|
||||||
b.setSmallIcon(R.drawable.notification_introduction);
|
b.setSmallIcon(R.drawable.notification_introduction);
|
||||||
b.setColorRes(R.color.briar_primary);
|
b.setColorRes(R.color.briar_primary);
|
||||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||||
@@ -599,9 +631,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
t.addNextIntent(i);
|
t.addNextIntent(i);
|
||||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||||
|
|
||||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
notificationManager.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID,
|
||||||
NotificationManager nm = (NotificationManager) o;
|
b.build());
|
||||||
nm.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID, b.build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
@@ -93,6 +94,7 @@ public class AppModule {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean databaseExists() {
|
public boolean databaseExists() {
|
||||||
|
// FIXME should not run on UiThread #620
|
||||||
if (!dir.isDirectory()) return false;
|
if (!dir.isDirectory()) return false;
|
||||||
File[] files = dir.listFiles();
|
File[] files = dir.listFiles();
|
||||||
return files != null && files.length > 0;
|
return files != null && files.length > 0;
|
||||||
@@ -157,6 +159,11 @@ public class AppModule {
|
|||||||
return devConfig;
|
return devConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
SharedPreferences provideSharedPreferences(Application app) {
|
||||||
|
return app.getSharedPreferences("db", MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
ReferenceManager provideReferenceManager() {
|
ReferenceManager provideReferenceManager() {
|
||||||
@@ -174,8 +181,11 @@ public class AppModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
ScreenFilterMonitor provideScreenFilterMonitor(
|
ScreenFilterMonitor provideScreenFilterMonitor(
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
ScreenFilterMonitorImpl screenFilterMonitor) {
|
ScreenFilterMonitorImpl screenFilterMonitor) {
|
||||||
|
lifecycleManager.registerService(screenFilterMonitor);
|
||||||
return screenFilterMonitor;
|
return screenFilterMonitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ package org.briarproject.briar.android;
|
|||||||
*/
|
*/
|
||||||
public interface BriarApplication {
|
public interface BriarApplication {
|
||||||
|
|
||||||
// This build expires on 31 December 2017
|
// This build expires on 30 April 2018
|
||||||
long EXPIRY_DATE = 1514761200 * 1000L;
|
long EXPIRY_DATE = 1525046400 * 1000L;
|
||||||
|
|
||||||
AndroidComponent getApplicationComponent();
|
AndroidComponent getApplicationComponent();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
@@ -7,7 +8,6 @@ import android.content.ComponentName;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
@@ -25,9 +25,12 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
|
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
||||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
||||||
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
||||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
||||||
@@ -37,9 +40,21 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
|
|||||||
|
|
||||||
public class BriarService extends Service {
|
public class BriarService extends Service {
|
||||||
|
|
||||||
|
public static String EXTRA_START_RESULT =
|
||||||
|
"org.briarproject.briar.START_RESULT";
|
||||||
|
public static String EXTRA_NOTIFICATION_ID =
|
||||||
|
"org.briarproject.briar.FAILURE_NOTIFICATION_ID";
|
||||||
|
public static String EXTRA_STARTUP_FAILED =
|
||||||
|
"org.briarproject.briar.STARTUP_FAILED";
|
||||||
|
|
||||||
private static final int ONGOING_NOTIFICATION_ID = 1;
|
private static final int ONGOING_NOTIFICATION_ID = 1;
|
||||||
private static final int FAILURE_NOTIFICATION_ID = 2;
|
private static final int FAILURE_NOTIFICATION_ID = 2;
|
||||||
|
|
||||||
|
// Channels are sorted by channel ID in the Settings app, so use IDs
|
||||||
|
// that will sort below the main channels such as contacts
|
||||||
|
private static final String ONGOING_CHANNEL_ID = "zForegroundService";
|
||||||
|
private static final String FAILURE_CHANNEL_ID = "zStartupFailure";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BriarService.class.getName());
|
Logger.getLogger(BriarService.class.getName());
|
||||||
|
|
||||||
@@ -73,8 +88,27 @@ public class BriarService extends Service {
|
|||||||
stopSelf();
|
stopSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create notification channels
|
||||||
|
if (SDK_INT >= 26) {
|
||||||
|
NotificationManager nm = (NotificationManager)
|
||||||
|
getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
NotificationChannel ongoingChannel = new NotificationChannel(
|
||||||
|
ONGOING_CHANNEL_ID,
|
||||||
|
getString(R.string.ongoing_notification_title),
|
||||||
|
IMPORTANCE_NONE);
|
||||||
|
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||||
|
nm.createNotificationChannel(ongoingChannel);
|
||||||
|
NotificationChannel failureChannel = new NotificationChannel(
|
||||||
|
FAILURE_CHANNEL_ID,
|
||||||
|
getString(R.string.startup_failed_notification_title),
|
||||||
|
IMPORTANCE_DEFAULT);
|
||||||
|
failureChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||||
|
nm.createNotificationChannel(failureChannel);
|
||||||
|
}
|
||||||
// Show an ongoing notification that the service is running
|
// Show an ongoing notification that the service is running
|
||||||
NotificationCompat.Builder b = new NotificationCompat.Builder(this);
|
NotificationCompat.Builder b =
|
||||||
|
new NotificationCompat.Builder(this, ONGOING_CHANNEL_ID);
|
||||||
b.setSmallIcon(R.drawable.notification_ongoing);
|
b.setSmallIcon(R.drawable.notification_ongoing);
|
||||||
b.setColor(ContextCompat.getColor(this, R.color.briar_primary));
|
b.setColor(ContextCompat.getColor(this, R.color.briar_primary));
|
||||||
b.setContentTitle(getText(R.string.ongoing_notification_title));
|
b.setContentTitle(getText(R.string.ongoing_notification_title));
|
||||||
@@ -84,7 +118,7 @@ public class BriarService extends Service {
|
|||||||
Intent i = new Intent(this, NavDrawerActivity.class);
|
Intent i = new Intent(this, NavDrawerActivity.class);
|
||||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
|
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (SDK_INT >= 21) {
|
||||||
b.setCategory(CATEGORY_SERVICE);
|
b.setCategory(CATEGORY_SERVICE);
|
||||||
b.setVisibility(VISIBILITY_SECRET);
|
b.setVisibility(VISIBILITY_SECRET);
|
||||||
}
|
}
|
||||||
@@ -113,8 +147,8 @@ public class BriarService extends Service {
|
|||||||
|
|
||||||
private void showStartupFailureNotification(StartResult result) {
|
private void showStartupFailureNotification(StartResult result) {
|
||||||
androidExecutor.runOnUiThread(() -> {
|
androidExecutor.runOnUiThread(() -> {
|
||||||
NotificationCompat.Builder b =
|
NotificationCompat.Builder b = new NotificationCompat.Builder(
|
||||||
new NotificationCompat.Builder(BriarService.this);
|
BriarService.this, FAILURE_CHANNEL_ID);
|
||||||
b.setSmallIcon(android.R.drawable.stat_notify_error);
|
b.setSmallIcon(android.R.drawable.stat_notify_error);
|
||||||
b.setContentTitle(getText(
|
b.setContentTitle(getText(
|
||||||
R.string.startup_failed_notification_title));
|
R.string.startup_failed_notification_title));
|
||||||
@@ -123,9 +157,8 @@ public class BriarService extends Service {
|
|||||||
Intent i = new Intent(BriarService.this,
|
Intent i = new Intent(BriarService.this,
|
||||||
StartupFailureActivity.class);
|
StartupFailureActivity.class);
|
||||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||||
i.putExtra("briar.START_RESULT", result);
|
i.putExtra(EXTRA_START_RESULT, result);
|
||||||
i.putExtra("briar.FAILURE_NOTIFICATION_ID",
|
i.putExtra(EXTRA_NOTIFICATION_ID, FAILURE_NOTIFICATION_ID);
|
||||||
FAILURE_NOTIFICATION_ID);
|
|
||||||
b.setContentIntent(PendingIntent.getActivity(BriarService.this,
|
b.setContentIntent(PendingIntent.getActivity(BriarService.this,
|
||||||
0, i, FLAG_UPDATE_CURRENT));
|
0, i, FLAG_UPDATE_CURRENT));
|
||||||
Object o = getSystemService(NOTIFICATION_SERVICE);
|
Object o = getSystemService(NOTIFICATION_SERVICE);
|
||||||
@@ -134,7 +167,7 @@ public class BriarService extends Service {
|
|||||||
// Bring the dashboard to the front to clear the back stack
|
// Bring the dashboard to the front to clear the back stack
|
||||||
i = new Intent(BriarService.this, NavDrawerActivity.class);
|
i = new Intent(BriarService.this, NavDrawerActivity.class);
|
||||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i.putExtra("briar.STARTUP_FAILED", true);
|
i.putExtra(EXTRA_STARTUP_FAILED, true);
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.content.pm.Signature;
|
import android.content.pm.Signature;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
|
||||||
@@ -16,23 +25,33 @@ import java.io.InputStream;
|
|||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_REPLACED;
|
||||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||||
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||||
|
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
|
||||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||||
import static android.content.pm.PackageManager.GET_SIGNATURES;
|
import static android.content.pm.PackageManager.GET_SIGNATURES;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
||||||
@@ -56,54 +75,93 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
|||||||
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
|
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
|
||||||
"0B145B6AA192858E79020103";
|
"0B145B6AA192858E79020103";
|
||||||
|
|
||||||
|
private static final String PREF_KEY_ALLOWED = "allowedOverlayApps";
|
||||||
|
|
||||||
private final PackageManager pm;
|
private final PackageManager pm;
|
||||||
|
private final Application app;
|
||||||
|
private final AndroidExecutor androidExecutor;
|
||||||
|
private final SharedPreferences prefs;
|
||||||
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
// UiThread
|
||||||
|
@Nullable
|
||||||
|
private BroadcastReceiver receiver = null;
|
||||||
|
|
||||||
|
// UiThread
|
||||||
|
@Nullable
|
||||||
|
private Collection<AppDetails> cachedApps = null;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ScreenFilterMonitorImpl(Application app) {
|
ScreenFilterMonitorImpl(Application app, AndroidExecutor androidExecutor,
|
||||||
|
SharedPreferences prefs) {
|
||||||
pm = app.getPackageManager();
|
pm = app.getPackageManager();
|
||||||
|
this.app = app;
|
||||||
|
this.androidExecutor = androidExecutor;
|
||||||
|
this.prefs = prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@UiThread
|
@UiThread
|
||||||
public Set<String> getApps() {
|
public Collection<AppDetails> getApps() {
|
||||||
Set<String> screenFilterApps = new TreeSet<>();
|
if (cachedApps != null) return cachedApps;
|
||||||
|
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
|
||||||
|
Collections.emptySet());
|
||||||
|
List<AppDetails> apps = new ArrayList<>();
|
||||||
List<PackageInfo> packageInfos =
|
List<PackageInfo> packageInfos =
|
||||||
pm.getInstalledPackages(GET_PERMISSIONS);
|
pm.getInstalledPackages(GET_PERMISSIONS);
|
||||||
for (PackageInfo packageInfo : packageInfos) {
|
for (PackageInfo packageInfo : packageInfos) {
|
||||||
if (isOverlayApp(packageInfo)) {
|
if (!allowed.contains(packageInfo.packageName)
|
||||||
String name = pkgToString(packageInfo);
|
&& isOverlayApp(packageInfo)) {
|
||||||
if (name != null) {
|
String name = getAppName(packageInfo);
|
||||||
screenFilterApps.add(name);
|
apps.add(new AppDetails(name, packageInfo.packageName));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return screenFilterApps;
|
Collections.sort(apps, (a, b) -> a.name.compareTo(b.name));
|
||||||
|
apps = Collections.unmodifiableList(apps);
|
||||||
|
cachedApps = apps;
|
||||||
|
return apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the application name for a given package.
|
@Override
|
||||||
@Nullable
|
@UiThread
|
||||||
private String pkgToString(PackageInfo pkgInfo) {
|
public void allowApps(Collection<String> packageNames) {
|
||||||
|
cachedApps = null;
|
||||||
|
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
|
||||||
|
Collections.emptySet());
|
||||||
|
Set<String> merged = new HashSet<>(allowed);
|
||||||
|
merged.addAll(packageNames);
|
||||||
|
prefs.edit().putStringSet(PREF_KEY_ALLOWED, merged).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the application name for a given package, or the package name
|
||||||
|
// if no application name is available
|
||||||
|
private String getAppName(PackageInfo pkgInfo) {
|
||||||
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
||||||
if (seq != null) {
|
return seq == null ? pkgInfo.packageName : seq.toString();
|
||||||
return seq.toString();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if an installed package is a user app using the permission.
|
// Checks if an installed package is a user app using the permission.
|
||||||
private boolean isOverlayApp(PackageInfo packageInfo) {
|
private boolean isOverlayApp(PackageInfo packageInfo) {
|
||||||
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
|
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
|
||||||
// Ignore system apps
|
// Ignore system apps
|
||||||
if ((packageInfo.applicationInfo.flags & mask) != 0) {
|
if ((packageInfo.applicationInfo.flags & mask) != 0) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Ignore Play Services, it's effectively a system app
|
// Ignore Play Services, it's effectively a system app
|
||||||
if (isPlayServices(packageInfo.packageName)) {
|
if (isPlayServices(packageInfo.packageName)) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Get permissions
|
// Get permissions
|
||||||
String[] requestedPermissions = packageInfo.requestedPermissions;
|
String[] requestedPermissions = packageInfo.requestedPermissions;
|
||||||
if (requestedPermissions != null) {
|
if (requestedPermissions == null) return false;
|
||||||
|
if (SDK_INT >= 16 && SDK_INT < 23) {
|
||||||
|
// Check whether the permission has been requested and granted
|
||||||
|
int[] flags = packageInfo.requestedPermissionsFlags;
|
||||||
|
for (int i = 0; i < requestedPermissions.length; i++) {
|
||||||
|
if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)) {
|
||||||
|
// 'flags' may be null on Robolectric
|
||||||
|
return flags == null ||
|
||||||
|
(flags[i] & REQUESTED_PERMISSION_GRANTED) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check whether the permission has been requested
|
||||||
for (String requestedPermission : requestedPermissions) {
|
for (String requestedPermission : requestedPermissions) {
|
||||||
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
|
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -113,6 +171,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("PackageManagerGetSignatures")
|
||||||
private boolean isPlayServices(String pkg) {
|
private boolean isPlayServices(String pkg) {
|
||||||
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
|
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
|
||||||
try {
|
try {
|
||||||
@@ -135,4 +194,36 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startService() throws ServiceException {
|
||||||
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
|
androidExecutor.runOnUiThread(() -> {
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_PACKAGE_ADDED);
|
||||||
|
filter.addAction(ACTION_PACKAGE_CHANGED);
|
||||||
|
filter.addAction(ACTION_PACKAGE_REMOVED);
|
||||||
|
filter.addAction(ACTION_PACKAGE_REPLACED);
|
||||||
|
filter.addDataScheme("package");
|
||||||
|
receiver = new PackageBroadcastReceiver();
|
||||||
|
app.registerReceiver(receiver, filter);
|
||||||
|
cachedApps = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopService() throws ServiceException {
|
||||||
|
androidExecutor.runOnUiThread(() -> {
|
||||||
|
if (receiver != null) app.unregisterReceiver(receiver);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PackageBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
cachedApps = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,32 +3,37 @@ package org.briarproject.briar.android;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
|
import org.briarproject.briar.android.fragment.ErrorFragment;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||||
|
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
|
||||||
|
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||||
|
|
||||||
public class StartupFailureActivity extends BaseActivity {
|
public class StartupFailureActivity extends BaseActivity implements
|
||||||
|
BaseFragmentListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle state) {
|
public void onCreate(Bundle state) {
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
setContentView(R.layout.activity_startup_failure);
|
setContentView(R.layout.activity_fragment_container);
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIntent(Intent i) {
|
private void handleIntent(Intent i) {
|
||||||
StartResult result = (StartResult) i.getSerializableExtra("briar.START_RESULT");
|
StartResult result =
|
||||||
int notificationId = i.getIntExtra("briar.FAILURE_NOTIFICATION_ID", -1);
|
(StartResult) i.getSerializableExtra(EXTRA_START_RESULT);
|
||||||
|
int notificationId = i.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
|
||||||
|
|
||||||
// cancel notification
|
// cancel notification
|
||||||
if (notificationId > -1) {
|
if (notificationId > -1) {
|
||||||
@@ -38,12 +43,31 @@ public class StartupFailureActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// show proper error message
|
// show proper error message
|
||||||
TextView view = findViewById(R.id.errorView);
|
String errorMsg;
|
||||||
if (result.equals(StartResult.DB_ERROR)) {
|
switch (result) {
|
||||||
view.setText(getText(R.string.startup_failed_db_error));
|
case DATA_TOO_OLD_ERROR:
|
||||||
} else if (result.equals(StartResult.SERVICE_ERROR)) {
|
errorMsg = getString(R.string.startup_failed_db_error);
|
||||||
view.setText(getText(R.string.startup_failed_service_error));
|
break;
|
||||||
|
case DATA_TOO_NEW_ERROR:
|
||||||
|
errorMsg =
|
||||||
|
getString(R.string.startup_failed_data_too_new_error);
|
||||||
|
break;
|
||||||
|
case DB_ERROR:
|
||||||
|
errorMsg =
|
||||||
|
getString(R.string.startup_failed_data_too_old_error);
|
||||||
|
break;
|
||||||
|
case SERVICE_ERROR:
|
||||||
|
errorMsg = getString(R.string.startup_failed_service_error);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
showInitialFragment(ErrorFragment.newInstance(errorMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runOnDbThread(Runnable runnable) {
|
||||||
|
throw new AssertionError("Deprecated and should not be used");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.activity;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
import org.briarproject.briar.android.AndroidComponent;
|
import org.briarproject.briar.android.AndroidComponent;
|
||||||
|
import org.briarproject.briar.android.StartupFailureActivity;
|
||||||
import org.briarproject.briar.android.blog.BlogActivity;
|
import org.briarproject.briar.android.blog.BlogActivity;
|
||||||
import org.briarproject.briar.android.blog.BlogFragment;
|
import org.briarproject.briar.android.blog.BlogFragment;
|
||||||
import org.briarproject.briar.android.blog.BlogModule;
|
import org.briarproject.briar.android.blog.BlogModule;
|
||||||
@@ -21,6 +22,7 @@ import org.briarproject.briar.android.forum.CreateForumActivity;
|
|||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
|
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||||
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||||
@@ -30,6 +32,7 @@ import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
|
|||||||
import org.briarproject.briar.android.login.AuthorNameFragment;
|
import org.briarproject.briar.android.login.AuthorNameFragment;
|
||||||
import org.briarproject.briar.android.login.ChangePasswordActivity;
|
import org.briarproject.briar.android.login.ChangePasswordActivity;
|
||||||
import org.briarproject.briar.android.login.DozeFragment;
|
import org.briarproject.briar.android.login.DozeFragment;
|
||||||
|
import org.briarproject.briar.android.login.OpenDatabaseActivity;
|
||||||
import org.briarproject.briar.android.login.PasswordActivity;
|
import org.briarproject.briar.android.login.PasswordActivity;
|
||||||
import org.briarproject.briar.android.login.PasswordFragment;
|
import org.briarproject.briar.android.login.PasswordFragment;
|
||||||
import org.briarproject.briar.android.login.SetupActivity;
|
import org.briarproject.briar.android.login.SetupActivity;
|
||||||
@@ -86,6 +89,8 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(SetupActivity activity);
|
void inject(SetupActivity activity);
|
||||||
|
|
||||||
|
void inject(OpenDatabaseActivity activity);
|
||||||
|
|
||||||
void inject(NavDrawerActivity activity);
|
void inject(NavDrawerActivity activity);
|
||||||
|
|
||||||
void inject(PasswordActivity activity);
|
void inject(PasswordActivity activity);
|
||||||
@@ -150,9 +155,13 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(RssFeedManageActivity activity);
|
void inject(RssFeedManageActivity activity);
|
||||||
|
|
||||||
|
void inject(StartupFailureActivity activity);
|
||||||
|
|
||||||
// Fragments
|
// Fragments
|
||||||
void inject(AuthorNameFragment fragment);
|
void inject(AuthorNameFragment fragment);
|
||||||
|
|
||||||
void inject(PasswordFragment fragment);
|
void inject(PasswordFragment fragment);
|
||||||
|
|
||||||
void inject(DozeFragment fragment);
|
void inject(DozeFragment fragment);
|
||||||
|
|
||||||
void inject(ContactListFragment fragment);
|
void inject(ContactListFragment fragment);
|
||||||
@@ -189,4 +198,5 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(SettingsFragment fragment);
|
void inject(SettingsFragment fragment);
|
||||||
|
|
||||||
|
void inject(ScreenFilterDialogFragment fragment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.activity;
|
package org.briarproject.briar.android.activity;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
import org.briarproject.briar.android.controller.BriarController;
|
import org.briarproject.briar.android.controller.BriarController;
|
||||||
import org.briarproject.briar.android.controller.BriarControllerImpl;
|
import org.briarproject.briar.android.controller.BriarControllerImpl;
|
||||||
@@ -19,7 +18,6 @@ import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
|
||||||
import static org.briarproject.briar.android.BriarService.BriarServiceConnection;
|
import static org.briarproject.briar.android.BriarService.BriarServiceConnection;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -57,12 +55,6 @@ public class ActivityModule {
|
|||||||
return configController;
|
return configController;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ActivityScope
|
|
||||||
@Provides
|
|
||||||
SharedPreferences provideSharedPreferences(Activity activity) {
|
|
||||||
return activity.getSharedPreferences("db", MODE_PRIVATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
@Provides
|
@Provides
|
||||||
PasswordController providePasswordController(
|
PasswordController providePasswordController(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.os.Bundle;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -20,13 +22,15 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
|||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||||
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
|
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
|
||||||
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
|
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
|
||||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -47,7 +51,10 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
private final List<ActivityLifecycleController> lifecycleControllers =
|
private final List<ActivityLifecycleController> lifecycleControllers =
|
||||||
new ArrayList<>();
|
new ArrayList<>();
|
||||||
private boolean destroyed = false;
|
private boolean destroyed = false;
|
||||||
private ScreenFilterDialogFragment dialogFrag;
|
|
||||||
|
@Nullable
|
||||||
|
private Toolbar toolbar = null;
|
||||||
|
private boolean searchedForToolbar = false;
|
||||||
|
|
||||||
public abstract void injectActivity(ActivityComponent component);
|
public abstract void injectActivity(ActivityComponent component);
|
||||||
|
|
||||||
@@ -56,8 +63,8 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(state);
|
||||||
|
|
||||||
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
||||||
|
|
||||||
@@ -96,6 +103,16 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||||
alc.onActivityStart();
|
alc.onActivityStart();
|
||||||
}
|
}
|
||||||
|
protectToolbar();
|
||||||
|
ScreenFilterDialogFragment f = findDialogFragment();
|
||||||
|
if (f != null) f.setDismissListener(this::protectToolbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ScreenFilterDialogFragment findDialogFragment() {
|
||||||
|
Fragment f = getSupportFragmentManager().findFragmentByTag(
|
||||||
|
ScreenFilterDialogFragment.TAG);
|
||||||
|
return (ScreenFilterDialogFragment) f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -106,15 +123,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
if (dialogFrag != null) {
|
|
||||||
dialogFrag.dismiss();
|
|
||||||
dialogFrag = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void showInitialFragment(BaseFragment f) {
|
protected void showInitialFragment(BaseFragment f) {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||||
@@ -131,14 +139,27 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showScreenFilterWarning() {
|
private boolean showScreenFilterWarning() {
|
||||||
if (dialogFrag != null && dialogFrag.isVisible()) return;
|
// If the dialog is already visible, filter the tap
|
||||||
Set<String> apps = screenFilterMonitor.getApps();
|
ScreenFilterDialogFragment f = findDialogFragment();
|
||||||
if (apps.isEmpty()) return;
|
if (f != null && f.isVisible()) return false;
|
||||||
dialogFrag =
|
Collection<AppDetails> apps = screenFilterMonitor.getApps();
|
||||||
ScreenFilterDialogFragment.newInstance(new ArrayList<>(apps));
|
// If all overlay apps have been allowed, allow the tap
|
||||||
dialogFrag.setCancelable(false);
|
if (apps.isEmpty()) return true;
|
||||||
dialogFrag.show(getSupportFragmentManager(), dialogFrag.getTag());
|
// Show dialog unless onSaveInstanceState() has been called, see #1112
|
||||||
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
|
if (!fm.isStateSaved()) {
|
||||||
|
// Create dialog
|
||||||
|
f = ScreenFilterDialogFragment.newInstance(apps);
|
||||||
|
// When dialog is dismissed, update protection of toolbar
|
||||||
|
f.setDismissListener(this::protectToolbar);
|
||||||
|
// Hide soft keyboard when (re)showing dialog
|
||||||
|
View focus = getCurrentFocus();
|
||||||
|
if (focus != null) hideSoftKeyboard(focus);
|
||||||
|
f.show(fm, ScreenFilterDialogFragment.TAG);
|
||||||
|
}
|
||||||
|
// Filter the tap
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -192,15 +213,25 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
* is outside the wrapper.
|
* is outside the wrapper.
|
||||||
*/
|
*/
|
||||||
private void protectToolbar() {
|
private void protectToolbar() {
|
||||||
View decorView = getWindow().getDecorView();
|
findToolbar();
|
||||||
if (decorView instanceof ViewGroup) {
|
if (toolbar != null) {
|
||||||
Toolbar toolbar = findToolbar((ViewGroup) decorView);
|
boolean filter = !screenFilterMonitor.getApps().isEmpty();
|
||||||
if (toolbar != null) toolbar.setFilterTouchesWhenObscured(true);
|
UiUtils.setFilterTouchesWhenObscured(toolbar, filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void findToolbar() {
|
||||||
|
if (searchedForToolbar) return;
|
||||||
|
View decorView = getWindow().getDecorView();
|
||||||
|
if (decorView instanceof ViewGroup)
|
||||||
|
toolbar = findToolbar((ViewGroup) decorView);
|
||||||
|
searchedForToolbar = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Toolbar findToolbar(ViewGroup vg) {
|
private Toolbar findToolbar(ViewGroup vg) {
|
||||||
|
// Views inside tap-safe layouts are already protected
|
||||||
|
if (vg instanceof TapSafeFrameLayout) return null;
|
||||||
for (int i = 0, len = vg.getChildCount(); i < len; i++) {
|
for (int i = 0, len = vg.getChildCount(); i < len; i++) {
|
||||||
View child = vg.getChildAt(i);
|
View child = vg.getChildAt(i);
|
||||||
if (child instanceof Toolbar) return (Toolbar) child;
|
if (child instanceof Toolbar) return (Toolbar) child;
|
||||||
@@ -220,23 +251,20 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
public void setContentView(View v) {
|
public void setContentView(View v) {
|
||||||
super.setContentView(makeTapSafeWrapper(v));
|
super.setContentView(makeTapSafeWrapper(v));
|
||||||
protectToolbar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setContentView(View v, LayoutParams layoutParams) {
|
public void setContentView(View v, LayoutParams layoutParams) {
|
||||||
super.setContentView(makeTapSafeWrapper(v), layoutParams);
|
super.setContentView(makeTapSafeWrapper(v), layoutParams);
|
||||||
protectToolbar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addContentView(View v, LayoutParams layoutParams) {
|
public void addContentView(View v, LayoutParams layoutParams) {
|
||||||
super.addContentView(makeTapSafeWrapper(v), layoutParams);
|
super.addContentView(makeTapSafeWrapper(v), layoutParams);
|
||||||
protectToolbar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTapFiltered() {
|
public boolean shouldAllowTap() {
|
||||||
showScreenFilterWarning();
|
return showScreenFilterWarning();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ import static android.os.Build.VERSION.SDK_INT;
|
|||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||||
|
|
||||||
@SuppressLint("Registered")
|
@SuppressLint("Registered")
|
||||||
public abstract class BriarActivity extends BaseActivity {
|
public abstract class BriarActivity extends BaseActivity {
|
||||||
|
|
||||||
public static final String KEY_STARTUP_FAILED = "briar.STARTUP_FAILED";
|
|
||||||
public static final String GROUP_ID = "briar.GROUP_ID";
|
public static final String GROUP_ID = "briar.GROUP_ID";
|
||||||
public static final String GROUP_NAME = "briar.GROUP_NAME";
|
public static final String GROUP_NAME = "briar.GROUP_NAME";
|
||||||
|
|
||||||
@@ -79,6 +79,10 @@ public abstract class BriarActivity extends BaseActivity {
|
|||||||
|
|
||||||
public void setSceneTransitionAnimation() {
|
public void setSceneTransitionAnimation() {
|
||||||
if (SDK_INT < 21) return;
|
if (SDK_INT < 21) return;
|
||||||
|
// workaround for #1007
|
||||||
|
if (isSamsung7()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Transition slide = new Slide(Gravity.RIGHT);
|
Transition slide = new Slide(Gravity.RIGHT);
|
||||||
slide.excludeTarget(android.R.id.statusBarBackground, true);
|
slide.excludeTarget(android.R.id.statusBarBackground, true);
|
||||||
slide.excludeTarget(android.R.id.navigationBarBackground, true);
|
slide.excludeTarget(android.R.id.navigationBarBackground, true);
|
||||||
@@ -126,8 +130,8 @@ public abstract class BriarActivity extends BaseActivity {
|
|||||||
b.setNegativeButton(R.string.cancel,
|
b.setNegativeButton(R.string.cancel,
|
||||||
(dialog, which) -> dialog.dismiss());
|
(dialog, which) -> dialog.dismiss());
|
||||||
b.setOnDismissListener(dialog -> {
|
b.setOnDismissListener(dialog -> {
|
||||||
CheckBox checkBox = (CheckBox) ((AlertDialog) dialog)
|
CheckBox checkBox =
|
||||||
.findViewById(R.id.checkbox);
|
((AlertDialog) dialog).findViewById(R.id.checkbox);
|
||||||
if (checkBox.isChecked())
|
if (checkBox.isChecked())
|
||||||
briarController.doNotAskAgainForDozeWhiteListing();
|
briarController.doNotAskAgainForDozeWhiteListing();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ public interface RequestCodes {
|
|||||||
int REQUEST_RINGTONE = 7;
|
int REQUEST_RINGTONE = 7;
|
||||||
int REQUEST_PERMISSION_CAMERA = 8;
|
int REQUEST_PERMISSION_CAMERA = 8;
|
||||||
int REQUEST_DOZE_WHITELISTING = 9;
|
int REQUEST_DOZE_WHITELISTING = 9;
|
||||||
|
int REQUEST_ENABLE_BLUETOOTH = 10;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ abstract class BasePostFragment extends BaseFragment {
|
|||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
getContext().startActivity(i);
|
getContext().startActivity(i);
|
||||||
}
|
}
|
||||||
});
|
}, getFragmentManager());
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||||
|
|
||||||
adapter = new BlogPostAdapter(getActivity(), this);
|
adapter =
|
||||||
|
new BlogPostAdapter(getActivity(), this, getFragmentManager());
|
||||||
list = v.findViewById(R.id.postList);
|
list = v.findViewById(R.id.postList);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
|
|||||||
@@ -1,21 +1,30 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.util.BriarAdapter;
|
import org.briarproject.briar.android.util.BriarAdapter;
|
||||||
|
|
||||||
class BlogPostAdapter
|
@MethodsNotNullByDefault
|
||||||
extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
|
@ParametersNotNullByDefault
|
||||||
|
class BlogPostAdapter extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
|
||||||
|
|
||||||
private final OnBlogPostClickListener listener;
|
private final OnBlogPostClickListener listener;
|
||||||
|
@Nullable
|
||||||
|
private final FragmentManager fragmentManager;
|
||||||
|
|
||||||
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener) {
|
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener,
|
||||||
|
@Nullable FragmentManager fragmentManager) {
|
||||||
super(ctx, BlogPostItem.class);
|
super(ctx, BlogPostItem.class);
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
|
this.fragmentManager = fragmentManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -23,8 +32,7 @@ class BlogPostAdapter
|
|||||||
int viewType) {
|
int viewType) {
|
||||||
View v = LayoutInflater.from(ctx).inflate(
|
View v = LayoutInflater.from(ctx).inflate(
|
||||||
R.layout.list_item_blog_post, parent, false);
|
R.layout.list_item_blog_post, parent, false);
|
||||||
BlogPostViewHolder ui = new BlogPostViewHolder(v, false, listener);
|
return new BlogPostViewHolder(v, false, listener, fragmentManager);
|
||||||
return ui;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.ActivityOptionsCompat;
|
import android.support.v4.app.ActivityOptionsCompat;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@@ -34,6 +35,7 @@ import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
|||||||
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
|
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
|
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
|
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
||||||
import static org.briarproject.briar.api.blog.MessageType.POST;
|
import static org.briarproject.briar.api.blog.MessageType.POST;
|
||||||
|
|
||||||
@@ -51,12 +53,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final OnBlogPostClickListener listener;
|
private final OnBlogPostClickListener listener;
|
||||||
|
@Nullable
|
||||||
|
private final FragmentManager fragmentManager;
|
||||||
|
|
||||||
BlogPostViewHolder(View v, boolean fullText,
|
BlogPostViewHolder(View v, boolean fullText,
|
||||||
@NonNull OnBlogPostClickListener listener) {
|
@NonNull OnBlogPostClickListener listener,
|
||||||
|
@Nullable FragmentManager fragmentManager) {
|
||||||
super(v);
|
super(v);
|
||||||
this.fullText = fullText;
|
this.fullText = fullText;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
|
this.fragmentManager = fragmentManager;
|
||||||
|
|
||||||
ctx = v.getContext();
|
ctx = v.getContext();
|
||||||
layout = v.findViewById(R.id.postLayout);
|
layout = v.findViewById(R.id.postLayout);
|
||||||
@@ -116,7 +122,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
if (fullText) {
|
if (fullText) {
|
||||||
body.setText(bodyText);
|
body.setText(bodyText);
|
||||||
body.setTextIsSelectable(true);
|
body.setTextIsSelectable(true);
|
||||||
makeLinksClickable(body);
|
makeLinksClickable(body, fragmentManager);
|
||||||
} else {
|
} else {
|
||||||
body.setTextIsSelectable(false);
|
body.setTextIsSelectable(false);
|
||||||
if (bodyText.length() > TEASER_LENGTH)
|
if (bodyText.length() > TEASER_LENGTH)
|
||||||
@@ -130,7 +136,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
|
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
|
||||||
i.putExtra(POST_ID, item.getId().getBytes());
|
i.putExtra(POST_ID, item.getId().getBytes());
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23 && !isSamsung7()) {
|
||||||
ActivityOptionsCompat options =
|
ActivityOptionsCompat options =
|
||||||
makeSceneTransitionAnimation((Activity) ctx, layout,
|
makeSceneTransitionAnimation((Activity) ctx, layout,
|
||||||
getTransitionName(item.getId()));
|
getTransitionName(item.getId()));
|
||||||
@@ -138,6 +144,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
options.toBundle());
|
options.toBundle());
|
||||||
} else {
|
} else {
|
||||||
// work-around for android bug #224270
|
// work-around for android bug #224270
|
||||||
|
// work-around for Samsung Android 7 bug #1007
|
||||||
ctx.startActivity(i);
|
ctx.startActivity(i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||||
|
|
||||||
adapter = new BlogPostAdapter(getActivity(), this);
|
adapter =
|
||||||
|
new BlogPostAdapter(getActivity(), this, getFragmentManager());
|
||||||
|
|
||||||
layoutManager = new LinearLayoutManager(getActivity());
|
layoutManager = new LinearLayoutManager(getActivity());
|
||||||
list = v.findViewById(R.id.postList);
|
list = v.findViewById(R.id.postList);
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
|||||||
ui.input.setVisibility(VISIBLE);
|
ui.input.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ViewHolder {
|
private class ViewHolder {
|
||||||
|
|
||||||
private final ScrollView scrollView;
|
private final ScrollView scrollView;
|
||||||
private final ProgressBar progressBar;
|
private final ProgressBar progressBar;
|
||||||
@@ -167,7 +167,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
|||||||
public void onAuthorClick(BlogPostItem post) {
|
public void onAuthorClick(BlogPostItem post) {
|
||||||
// probably don't want to allow author clicks here
|
// probably don't want to allow author clicks here
|
||||||
}
|
}
|
||||||
});
|
}, getFragmentManager());
|
||||||
input = v.findViewById(R.id.inputText);
|
input = v.findViewById(R.id.inputText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,4 +17,7 @@ public interface ConfigController {
|
|||||||
void deleteAccount(Context ctx);
|
void deleteAccount(Context ctx);
|
||||||
|
|
||||||
boolean accountExists();
|
boolean accountExists();
|
||||||
|
|
||||||
|
boolean accountSignedIn();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,4 +52,10 @@ public class ConfigControllerImpl implements ConfigController {
|
|||||||
String hex = getEncryptedDatabaseKey();
|
String hex = getEncryptedDatabaseKey();
|
||||||
return hex != null && databaseConfig.databaseExists();
|
return hex != null && databaseConfig.databaseExists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accountSignedIn() {
|
||||||
|
return databaseConfig.getEncryptionKey() != null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.briarproject.briar.android.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class ErrorFragment extends BaseFragment {
|
||||||
|
|
||||||
|
private static final String TAG = ErrorFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String ERROR_MSG = "errorMessage";
|
||||||
|
|
||||||
|
public static ErrorFragment newInstance(String message) {
|
||||||
|
ErrorFragment f = new ErrorFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(ERROR_MSG, message);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Bundle args = getArguments();
|
||||||
|
if (args == null) throw new AssertionError();
|
||||||
|
errorMessage = args.getString(ERROR_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
View v = inflater
|
||||||
|
.inflate(R.layout.fragment_error, container, false);
|
||||||
|
TextView msg = v.findViewById(R.id.errorMessage);
|
||||||
|
msg.setText(errorMessage);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
// not necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,40 +1,106 @@
|
|||||||
package org.briarproject.briar.android.fragment;
|
package org.briarproject.briar.android.fragment;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@NotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
public class ScreenFilterDialogFragment extends DialogFragment {
|
public class ScreenFilterDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
|
public static final String TAG = ScreenFilterDialogFragment.class.getName();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ScreenFilterMonitor screenFilterMonitor;
|
||||||
|
|
||||||
|
DismissListener dismissListener = null;
|
||||||
|
|
||||||
public static ScreenFilterDialogFragment newInstance(
|
public static ScreenFilterDialogFragment newInstance(
|
||||||
ArrayList<String> apps) {
|
Collection<AppDetails> apps) {
|
||||||
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
|
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putStringArrayList("apps", apps);
|
ArrayList<String> appNames = new ArrayList<>();
|
||||||
|
for (AppDetails a : apps) appNames.add(a.name);
|
||||||
|
args.putStringArrayList("appNames", appNames);
|
||||||
|
ArrayList<String> packageNames = new ArrayList<>();
|
||||||
|
for (AppDetails a : apps) packageNames.add(a.packageName);
|
||||||
|
args.putStringArrayList("packageNames", packageNames);
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDismissListener(DismissListener dismissListener) {
|
||||||
|
this.dismissListener = dismissListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) throw new IllegalStateException();
|
||||||
|
((BaseActivity) activity).getActivityComponent().inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) throw new IllegalStateException();
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity,
|
||||||
R.style.BriarDialogThemeNoFilter);
|
R.style.BriarDialogThemeNoFilter);
|
||||||
builder.setTitle(R.string.screen_filter_title);
|
builder.setTitle(R.string.screen_filter_title);
|
||||||
ArrayList<String> apps = getArguments().getStringArrayList("apps");
|
Bundle args = getArguments();
|
||||||
builder.setMessage(getString(R.string.screen_filter_body,
|
if (args == null) throw new IllegalStateException();
|
||||||
TextUtils.join("\n", apps)));
|
ArrayList<String> appNames = args.getStringArrayList("appNames");
|
||||||
builder.setNeutralButton(R.string.continue_button,
|
ArrayList<String> packageNames =
|
||||||
(dialog, which) -> dialog.dismiss());
|
args.getStringArrayList("packageNames");
|
||||||
|
if (appNames == null || packageNames == null)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
LayoutInflater inflater = activity.getLayoutInflater();
|
||||||
|
// See https://stackoverflow.com/a/24720976/6314875
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
View dialogView = inflater.inflate(R.layout.dialog_screen_filter, null);
|
||||||
|
builder.setView(dialogView);
|
||||||
|
TextView message = dialogView.findViewById(R.id.screen_filter_message);
|
||||||
|
message.setText(getString(R.string.screen_filter_body,
|
||||||
|
TextUtils.join("\n", appNames)));
|
||||||
|
CheckBox allow = dialogView.findViewById(R.id.screen_filter_checkbox);
|
||||||
|
builder.setNeutralButton(R.string.continue_button, (dialog, which) -> {
|
||||||
|
if (allow.isChecked()) screenFilterMonitor.allowApps(packageNames);
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
builder.setCancelable(false);
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialog) {
|
||||||
|
super.onDismiss(dialog);
|
||||||
|
if (dismissListener != null) dismissListener.onDialogDismissed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DismissListener {
|
||||||
|
void onDialogDismissed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import android.hardware.Camera.AutoFocusCallback;
|
|||||||
import android.hardware.Camera.CameraInfo;
|
import android.hardware.Camera.CameraInfo;
|
||||||
import android.hardware.Camera.Parameters;
|
import android.hardware.Camera.Parameters;
|
||||||
import android.hardware.Camera.Size;
|
import android.hardware.Camera.Size;
|
||||||
import android.os.Build;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
@@ -21,6 +21,7 @@ import java.io.IOException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK;
|
||||||
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
|
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||||
import static android.hardware.Camera.Parameters.FLASH_MODE_OFF;
|
import static android.hardware.Camera.Parameters.FLASH_MODE_OFF;
|
||||||
import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO;
|
import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO;
|
||||||
@@ -31,6 +32,7 @@ import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED;
|
|||||||
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
|
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
|
||||||
import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
|
import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
|
||||||
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
|
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@@ -38,18 +40,27 @@ import static java.util.logging.Level.WARNING;
|
|||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||||
AutoFocusCallback {
|
AutoFocusCallback, View.OnClickListener {
|
||||||
|
|
||||||
|
// Heuristic for the ideal preview size - small previews don't have enough
|
||||||
|
// detail, large previews are slow to decode
|
||||||
|
private static final int IDEAL_PIXELS = 500 * 1000;
|
||||||
|
|
||||||
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(CameraView.class.getName());
|
Logger.getLogger(CameraView.class.getName());
|
||||||
|
|
||||||
|
private final Runnable autoFocusRetry = this::retryAutoFocus;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Camera camera = null;
|
private Camera camera = null;
|
||||||
|
private int cameraIndex = 0;
|
||||||
private PreviewConsumer previewConsumer = null;
|
private PreviewConsumer previewConsumer = null;
|
||||||
private Surface surface = null;
|
private Surface surface = null;
|
||||||
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
|
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
|
||||||
private boolean previewStarted = false, autoFocus = false;
|
private boolean previewStarted = false;
|
||||||
|
private boolean autoFocusSupported = false, autoFocusRunning = false;
|
||||||
|
|
||||||
public CameraView(Context context) {
|
public CameraView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -74,6 +85,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
setKeepScreenOn(true);
|
setKeepScreenOn(true);
|
||||||
getHolder().addCallback(this);
|
getHolder().addCallback(this);
|
||||||
|
setOnClickListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -84,15 +96,32 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public void start() throws CameraException {
|
public void start(int rotationDegrees) throws CameraException {
|
||||||
LOG.info("Opening camera");
|
LOG.info("Opening camera");
|
||||||
try {
|
try {
|
||||||
camera = Camera.open();
|
int cameras = Camera.getNumberOfCameras();
|
||||||
|
if (cameras == 0) throw new CameraException("No camera");
|
||||||
|
// Try to find a back-facing camera
|
||||||
|
for (int i = 0; i < cameras; i++) {
|
||||||
|
CameraInfo info = new CameraInfo();
|
||||||
|
Camera.getCameraInfo(i, info);
|
||||||
|
if (info.facing == CAMERA_FACING_BACK) {
|
||||||
|
LOG.info("Using back-facing camera");
|
||||||
|
camera = Camera.open(i);
|
||||||
|
cameraIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we can't find a back-facing camera, use a front-facing one
|
||||||
|
if (camera == null) {
|
||||||
|
LOG.info("Using front-facing camera");
|
||||||
|
camera = Camera.open(0);
|
||||||
|
cameraIndex = 0;
|
||||||
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
if (camera == null) throw new CameraException("No back-facing camera");
|
setDisplayOrientation(rotationDegrees);
|
||||||
setDisplayOrientation(0);
|
|
||||||
// Use barcode scene mode if it's available
|
// Use barcode scene mode if it's available
|
||||||
Parameters params = camera.getParameters();
|
Parameters params = camera.getParameters();
|
||||||
params = setSceneMode(camera, params);
|
params = setSceneMode(camera, params);
|
||||||
@@ -157,36 +186,54 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
@UiThread
|
@UiThread
|
||||||
private void startConsumer() throws CameraException {
|
private void startConsumer() throws CameraException {
|
||||||
if (camera == null) throw new CameraException("Camera is null");
|
if (camera == null) throw new CameraException("Camera is null");
|
||||||
if (autoFocus) {
|
startAutoFocus();
|
||||||
|
previewConsumer.start(camera, cameraIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void startAutoFocus() throws CameraException {
|
||||||
|
if (camera != null && autoFocusSupported && !autoFocusRunning) {
|
||||||
try {
|
try {
|
||||||
|
removeCallbacks(autoFocusRetry);
|
||||||
camera.autoFocus(this);
|
camera.autoFocus(this);
|
||||||
|
autoFocusRunning = true;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previewConsumer.start(camera);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void stopConsumer() throws CameraException {
|
private void stopConsumer() throws CameraException {
|
||||||
if (camera == null) throw new CameraException("Camera is null");
|
if (camera == null) throw new CameraException("Camera is null");
|
||||||
if (autoFocus) {
|
cancelAutoFocus();
|
||||||
try {
|
|
||||||
camera.cancelAutoFocus();
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
throw new CameraException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
previewConsumer.stop();
|
previewConsumer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void cancelAutoFocus() throws CameraException {
|
||||||
|
if (camera != null && autoFocusSupported && autoFocusRunning) {
|
||||||
|
try {
|
||||||
|
removeCallbacks(autoFocusRetry);
|
||||||
|
camera.cancelAutoFocus();
|
||||||
|
autoFocusRunning = false;
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw new CameraException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link Camera#setDisplayOrientation(int)}.
|
||||||
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
private void setDisplayOrientation(int rotationDegrees)
|
private void setDisplayOrientation(int rotationDegrees)
|
||||||
throws CameraException {
|
throws CameraException {
|
||||||
|
if (camera == null) throw new CameraException("Camera is null");
|
||||||
int orientation;
|
int orientation;
|
||||||
CameraInfo info = new CameraInfo();
|
CameraInfo info = new CameraInfo();
|
||||||
try {
|
try {
|
||||||
Camera.getCameraInfo(0, info);
|
Camera.getCameraInfo(cameraIndex, info);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
@@ -196,9 +243,11 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
} else {
|
} else {
|
||||||
orientation = (info.orientation - rotationDegrees + 360) % 360;
|
orientation = (info.orientation - rotationDegrees + 360) % 360;
|
||||||
}
|
}
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Display orientation " + orientation + " degrees");
|
LOG.info("Screen rotation " + rotationDegrees
|
||||||
if (camera == null) throw new CameraException("Camera is null");
|
+ " degrees, camera orientation " + orientation
|
||||||
|
+ " degrees");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
camera.setDisplayOrientation(orientation);
|
camera.setDisplayOrientation(orientation);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
@@ -266,8 +315,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void setVideoStabilisation(Parameters params) {
|
private void setVideoStabilisation(Parameters params) {
|
||||||
if (Build.VERSION.SDK_INT >= 15 &&
|
if (SDK_INT >= 15 && params.isVideoStabilizationSupported()) {
|
||||||
params.isVideoStabilizationSupported()) {
|
|
||||||
params.setVideoStabilization(true);
|
params.setVideoStabilization(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,6 +342,8 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
@UiThread
|
@UiThread
|
||||||
private void setPreviewSize(Parameters params) {
|
private void setPreviewSize(Parameters params) {
|
||||||
if (surfaceWidth == 0 || surfaceHeight == 0) return;
|
if (surfaceWidth == 0 || surfaceHeight == 0) return;
|
||||||
|
// Choose a preview size that's close to the aspect ratio of the
|
||||||
|
// surface and close to the ideal size for decoding
|
||||||
float idealRatio = (float) surfaceWidth / surfaceHeight;
|
float idealRatio = (float) surfaceWidth / surfaceHeight;
|
||||||
boolean rotatePreview = displayOrientation % 180 == 90;
|
boolean rotatePreview = displayOrientation % 180 == 90;
|
||||||
List<Size> sizes = params.getSupportedPreviewSizes();
|
List<Size> sizes = params.getSupportedPreviewSizes();
|
||||||
@@ -304,11 +354,12 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
int height = rotatePreview ? size.width : size.height;
|
int height = rotatePreview ? size.width : size.height;
|
||||||
float ratio = (float) width / height;
|
float ratio = (float) width / height;
|
||||||
float stretch = Math.max(ratio / idealRatio, idealRatio / ratio);
|
float stretch = Math.max(ratio / idealRatio, idealRatio / ratio);
|
||||||
int pixels = width * height;
|
float pixels = width * height;
|
||||||
float score = pixels / stretch;
|
float zoom = Math.max(pixels / IDEAL_PIXELS, IDEAL_PIXELS / pixels);
|
||||||
|
float score = 1 / (stretch * zoom);
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Size " + size.width + "x" + size.height
|
LOG.info("Size " + size.width + "x" + size.height
|
||||||
+ ", stretch " + stretch + ", pixels " + pixels
|
+ ", stretch " + stretch + ", zoom " + zoom
|
||||||
+ ", score " + score);
|
+ ", score " + score);
|
||||||
}
|
}
|
||||||
if (bestSize == null || score > bestScore) {
|
if (bestSize == null || score > bestScore) {
|
||||||
@@ -325,7 +376,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void enableAutoFocus(String focusMode) {
|
private void enableAutoFocus(String focusMode) {
|
||||||
autoFocus = FOCUS_MODE_AUTO.equals(focusMode) ||
|
autoFocusSupported = FOCUS_MODE_AUTO.equals(focusMode) ||
|
||||||
FOCUS_MODE_MACRO.equals(focusMode);
|
FOCUS_MODE_MACRO.equals(focusMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +390,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= 15) {
|
if (SDK_INT >= 15) {
|
||||||
LOG.info("Video stabilisation enabled: "
|
LOG.info("Video stabilisation enabled: "
|
||||||
+ params.getVideoStabilization());
|
+ params.getVideoStabilization());
|
||||||
}
|
}
|
||||||
@@ -370,8 +421,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
surface.release();
|
surface.release();
|
||||||
}
|
}
|
||||||
surface = holder.getSurface();
|
surface = holder.getSurface();
|
||||||
// Start the preview when the camera and the surface are both ready
|
// We'll start the preview when surfaceChanged() is called
|
||||||
if (camera != null && !previewStarted) startPreview(holder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -397,7 +447,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
surfaceWidth = w;
|
surfaceWidth = w;
|
||||||
surfaceHeight = h;
|
surfaceHeight = h;
|
||||||
if (camera == null) return; // We are stopped
|
if (camera == null) return; // We are stopped
|
||||||
stopPreview();
|
if (previewStarted) stopPreview();
|
||||||
try {
|
try {
|
||||||
Parameters params = camera.getParameters();
|
Parameters params = camera.getParameters();
|
||||||
setPreviewSize(params);
|
setPreviewSize(params);
|
||||||
@@ -427,16 +477,23 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAutoFocus(boolean success, Camera camera) {
|
public void onAutoFocus(boolean success, Camera camera) {
|
||||||
LOG.info("Auto focus succeeded: " + success);
|
if (LOG.isLoggable(INFO))
|
||||||
postDelayed(this::retryAutoFocus, AUTO_FOCUS_RETRY_DELAY);
|
LOG.info("Auto focus succeeded: " + success);
|
||||||
|
autoFocusRunning = false;
|
||||||
|
postDelayed(autoFocusRetry, AUTO_FOCUS_RETRY_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void retryAutoFocus() {
|
private void retryAutoFocus() {
|
||||||
try {
|
try {
|
||||||
if (camera != null) camera.autoFocus(this);
|
startAutoFocus();
|
||||||
} catch (RuntimeException e) {
|
} catch (CameraException e) {
|
||||||
LOG.log(WARNING, "Error retrying auto focus", e);
|
LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
retryAutoFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
package org.briarproject.briar.android.keyagreement;
|
package org.briarproject.briar.android.keyagreement;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
@@ -23,6 +28,7 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
|||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.R.string;
|
import org.briarproject.briar.R.string;
|
||||||
import org.briarproject.briar.R.style;
|
import org.briarproject.briar.R.style;
|
||||||
@@ -39,9 +45,15 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.Manifest.permission.CAMERA;
|
import static android.Manifest.permission.CAMERA;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -50,6 +62,10 @@ public class KeyAgreementActivity extends BriarActivity implements
|
|||||||
BaseFragmentListener, IntroScreenSeenListener, EventListener,
|
BaseFragmentListener, IntroScreenSeenListener, EventListener,
|
||||||
ContactExchangeListener {
|
ContactExchangeListener {
|
||||||
|
|
||||||
|
private enum BluetoothState {
|
||||||
|
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED
|
||||||
|
}
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(KeyAgreementActivity.class.getName());
|
Logger.getLogger(KeyAgreementActivity.class.getName());
|
||||||
|
|
||||||
@@ -62,7 +78,10 @@ public class KeyAgreementActivity extends BriarActivity implements
|
|||||||
@Inject
|
@Inject
|
||||||
volatile IdentityManager identityManager;
|
volatile IdentityManager identityManager;
|
||||||
|
|
||||||
|
private boolean isResumed = false, enableWasRequested = false;
|
||||||
private boolean continueClicked, gotCameraPermission;
|
private boolean continueClicked, gotCameraPermission;
|
||||||
|
private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
|
||||||
|
private BroadcastReceiver bluetoothReceiver = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void injectActivity(ActivityComponent component) {
|
||||||
@@ -84,6 +103,15 @@ public class KeyAgreementActivity extends BriarActivity implements
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
showInitialFragment(IntroFragment.newInstance());
|
showInitialFragment(IntroFragment.newInstance());
|
||||||
}
|
}
|
||||||
|
IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED);
|
||||||
|
bluetoothReceiver = new BluetoothStateReceiver();
|
||||||
|
registerReceiver(bluetoothReceiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -112,24 +140,72 @@ public class KeyAgreementActivity extends BriarActivity implements
|
|||||||
@Override
|
@Override
|
||||||
protected void onPostResume() {
|
protected void onPostResume() {
|
||||||
super.onPostResume();
|
super.onPostResume();
|
||||||
|
isResumed = true;
|
||||||
// Workaround for
|
// Workaround for
|
||||||
// https://code.google.com/p/android/issues/detail?id=190966
|
// https://code.google.com/p/android/issues/detail?id=190966
|
||||||
if (continueClicked && gotCameraPermission) {
|
if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||||
showQrCodeFragment();
|
}
|
||||||
}
|
|
||||||
|
boolean canShowQrCodeFragment() {
|
||||||
|
return isResumed && continueClicked
|
||||||
|
&& (SDK_INT < 23 || gotCameraPermission)
|
||||||
|
&& bluetoothState != BluetoothState.UNKNOWN
|
||||||
|
&& bluetoothState != BluetoothState.WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
isResumed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showNextScreen() {
|
public void showNextScreen() {
|
||||||
// FIXME #824
|
|
||||||
// showNextFragment(ShowQrCodeFragment.newInstance());
|
|
||||||
continueClicked = true;
|
continueClicked = true;
|
||||||
if (checkPermissions()) {
|
if (checkPermissions()) {
|
||||||
showQrCodeFragment();
|
if (shouldRequestEnableBluetooth()) requestEnableBluetooth();
|
||||||
|
else if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldRequestEnableBluetooth() {
|
||||||
|
return bluetoothState == BluetoothState.UNKNOWN
|
||||||
|
|| bluetoothState == BluetoothState.REFUSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestEnableBluetooth() {
|
||||||
|
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
if (bt == null) {
|
||||||
|
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||||
|
} else if (bt.isEnabled()) {
|
||||||
|
setBluetoothState(BluetoothState.ENABLED);
|
||||||
|
} else {
|
||||||
|
enableWasRequested = true;
|
||||||
|
setBluetoothState(BluetoothState.WAITING);
|
||||||
|
Intent i = new Intent(ACTION_REQUEST_ENABLE);
|
||||||
|
startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBluetoothState(BluetoothState bluetoothState) {
|
||||||
|
LOG.info("Setting Bluetooth state to " + bluetoothState);
|
||||||
|
this.bluetoothState = bluetoothState;
|
||||||
|
if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) {
|
||||||
|
eventBus.broadcast(new BluetoothEnabledEvent());
|
||||||
|
enableWasRequested = false;
|
||||||
|
}
|
||||||
|
if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int request, int result, Intent data) {
|
||||||
|
// If the request was granted we'll catch the state change event
|
||||||
|
if (request == REQUEST_ENABLE_BLUETOOTH && result == RESULT_CANCELED)
|
||||||
|
setBluetoothState(BluetoothState.REFUSED);
|
||||||
|
}
|
||||||
|
|
||||||
private void showQrCodeFragment() {
|
private void showQrCodeFragment() {
|
||||||
|
// FIXME #824
|
||||||
BaseFragment f = ShowQrCodeFragment.newInstance();
|
BaseFragment f = ShowQrCodeFragment.newInstance();
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||||
@@ -154,8 +230,10 @@ public class KeyAgreementActivity extends BriarActivity implements
|
|||||||
} else {
|
} else {
|
||||||
requestPermission();
|
requestPermission();
|
||||||
}
|
}
|
||||||
|
gotCameraPermission = false;
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
gotCameraPermission = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,6 +252,7 @@ public class KeyAgreementActivity extends BriarActivity implements
|
|||||||
if (grantResults.length > 0 &&
|
if (grantResults.length > 0 &&
|
||||||
grantResults[0] == PERMISSION_GRANTED) {
|
grantResults[0] == PERMISSION_GRANTED) {
|
||||||
gotCameraPermission = true;
|
gotCameraPermission = true;
|
||||||
|
showNextScreen();
|
||||||
} else {
|
} else {
|
||||||
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
|
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||||
CAMERA)) {
|
CAMERA)) {
|
||||||
@@ -258,4 +337,14 @@ public class KeyAgreementActivity extends BriarActivity implements
|
|||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||||
|
if (state == STATE_ON) setBluetoothState(BluetoothState.ENABLED);
|
||||||
|
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
interface PreviewConsumer {
|
interface PreviewConsumer {
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
void start(Camera camera);
|
void start(Camera camera, int cameraIndex);
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
void stop();
|
void stop();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android.keyagreement;
|
package org.briarproject.briar.android.keyagreement;
|
||||||
|
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
|
import android.hardware.Camera.CameraInfo;
|
||||||
import android.hardware.Camera.PreviewCallback;
|
import android.hardware.Camera.PreviewCallback;
|
||||||
import android.hardware.Camera.Size;
|
import android.hardware.Camera.Size;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
@@ -36,20 +37,23 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
private final ResultCallback callback;
|
private final ResultCallback callback;
|
||||||
|
|
||||||
private Camera camera = null;
|
private Camera camera = null;
|
||||||
|
private int cameraIndex = 0;
|
||||||
|
|
||||||
QrCodeDecoder(ResultCallback callback) {
|
QrCodeDecoder(ResultCallback callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Camera camera) {
|
public void start(Camera camera, int cameraIndex) {
|
||||||
this.camera = camera;
|
this.camera = camera;
|
||||||
|
this.cameraIndex = cameraIndex;
|
||||||
askForPreviewFrame();
|
askForPreviewFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
camera = null;
|
camera = null;
|
||||||
|
cameraIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -61,45 +65,64 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||||
if (camera == this.camera) {
|
if (camera == this.camera) {
|
||||||
|
LOG.info("Got preview frame");
|
||||||
try {
|
try {
|
||||||
Size size = camera.getParameters().getPreviewSize();
|
Size size = camera.getParameters().getPreviewSize();
|
||||||
new DecoderTask(data, size.width, size.height).execute();
|
// The preview should be in NV21 format: width * height bytes of
|
||||||
|
// Y followed by width * height / 2 bytes of interleaved U and V
|
||||||
|
if (data.length == size.width * size.height * 3 / 2) {
|
||||||
|
CameraInfo info = new CameraInfo();
|
||||||
|
Camera.getCameraInfo(cameraIndex, info);
|
||||||
|
new DecoderTask(data, size.width, size.height,
|
||||||
|
info.orientation).execute();
|
||||||
|
} else {
|
||||||
|
// Camera parameters have changed - ask for a new preview
|
||||||
|
LOG.info("Preview size does not match camera parameters");
|
||||||
|
askForPreviewFrame();
|
||||||
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.log(WARNING, "Error getting camera parameters.", e);
|
LOG.log(WARNING, "Error getting camera parameters.", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("Camera has changed, ignoring preview frame");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DecoderTask extends AsyncTask<Void, Void, Void> {
|
private class DecoderTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
private final byte[] data;
|
private final byte[] data;
|
||||||
private final int width, height;
|
private final int width, height, orientation;
|
||||||
|
|
||||||
private DecoderTask(byte[] data, int width, int height) {
|
private DecoderTask(byte[] data, int width, int height,
|
||||||
|
int orientation) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
this.orientation = orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
BinaryBitmap bitmap = binarize(data, width, height, orientation);
|
||||||
height, 0, 0, width, height, false);
|
|
||||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
|
|
||||||
Result result = null;
|
Result result = null;
|
||||||
try {
|
try {
|
||||||
result = reader.decode(bitmap);
|
result = reader.decode(bitmap);
|
||||||
|
long duration = System.currentTimeMillis() - now;
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Decoding barcode took " + duration + " ms");
|
||||||
} catch (ReaderException e) {
|
} catch (ReaderException e) {
|
||||||
return null; // No barcode found
|
// No barcode found
|
||||||
|
long duration = System.currentTimeMillis() - now;
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("No barcode found after " + duration + " ms");
|
||||||
|
return null;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return null; // Preview data did not match width and height
|
LOG.warning("Invalid preview frame");
|
||||||
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
reader.reset();
|
reader.reset();
|
||||||
}
|
}
|
||||||
long duration = System.currentTimeMillis() - now;
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Decoding barcode took " + duration + " ms");
|
|
||||||
callback.handleResult(result);
|
callback.handleResult(result);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -110,6 +133,19 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BinaryBitmap binarize(byte[] data, int width, int height,
|
||||||
|
int orientation) {
|
||||||
|
// Crop to a square at the top (portrait) or left (landscape) of the
|
||||||
|
// screen - this will be faster to decode and should include
|
||||||
|
// everything visible in the viewfinder
|
||||||
|
int crop = Math.min(width, height);
|
||||||
|
int left = orientation >= 180 ? width - crop : 0;
|
||||||
|
int top = orientation >= 180 ? height - crop : 0;
|
||||||
|
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
||||||
|
height, left, top, crop, crop, false);
|
||||||
|
return new BinaryBitmap(new HybridBinarizer(src));
|
||||||
|
}
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface ResultCallback {
|
interface ResultCallback {
|
||||||
|
|
||||||
|
|||||||
@@ -32,21 +32,24 @@ class QrCodeUtils {
|
|||||||
// Generate QR code
|
// Generate QR code
|
||||||
BitMatrix encoded = new QRCodeWriter().encode(input, QR_CODE,
|
BitMatrix encoded = new QRCodeWriter().encode(input, QR_CODE,
|
||||||
smallestDimen, smallestDimen);
|
smallestDimen, smallestDimen);
|
||||||
// Convert QR code to Bitmap
|
return renderQrCode(encoded);
|
||||||
int width = encoded.getWidth();
|
|
||||||
int height = encoded.getHeight();
|
|
||||||
int[] pixels = new int[width * height];
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
pixels[y * width + x] = encoded.get(x, y) ? BLACK : WHITE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888);
|
|
||||||
qr.setPixels(pixels, 0, width, 0, 0, width, height);
|
|
||||||
return qr;
|
|
||||||
} catch (WriterException e) {
|
} catch (WriterException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Bitmap renderQrCode(BitMatrix matrix) {
|
||||||
|
int width = matrix.getWidth();
|
||||||
|
int height = matrix.getHeight();
|
||||||
|
int[] pixels = new int[width * height];
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
pixels[y * width + x] = matrix.get(x, y) ? BLACK : WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888);
|
||||||
|
qr.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||||
|
return qr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
package org.briarproject.briar.android.keyagreement;
|
package org.briarproject.briar.android.keyagreement;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.hardware.Camera;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.Display;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.animation.AlphaAnimation;
|
||||||
@@ -37,7 +36,6 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
|||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.fragment.BaseEventFragment;
|
import org.briarproject.briar.android.fragment.BaseEventFragment;
|
||||||
@@ -49,9 +47,6 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
|
||||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
@@ -86,8 +81,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
private TextView mainProgressTitle;
|
private TextView mainProgressTitle;
|
||||||
private ViewGroup mainProgressContainer;
|
private ViewGroup mainProgressContainer;
|
||||||
|
|
||||||
private BluetoothStateReceiver receiver;
|
private boolean gotRemotePayload;
|
||||||
private boolean gotRemotePayload, waitingForBluetooth;
|
|
||||||
private volatile boolean gotLocalPayload;
|
private volatile boolean gotLocalPayload;
|
||||||
private KeyAgreementTask task;
|
private KeyAgreementTask task;
|
||||||
|
|
||||||
@@ -144,23 +138,29 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
try {
|
try {
|
||||||
cameraView.start();
|
cameraView.start(getScreenRotationDegrees());
|
||||||
} catch (CameraException e) {
|
} catch (CameraException e) {
|
||||||
logCameraExceptionAndFinish(e);
|
logCameraExceptionAndFinish(e);
|
||||||
}
|
}
|
||||||
// Listen for changes to the Bluetooth state
|
startListening();
|
||||||
IntentFilter filter = new IntentFilter();
|
}
|
||||||
filter.addAction(ACTION_STATE_CHANGED);
|
|
||||||
receiver = new BluetoothStateReceiver();
|
|
||||||
getActivity().registerReceiver(receiver, filter);
|
|
||||||
|
|
||||||
// Enable BT adapter if it is not already on.
|
/**
|
||||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
* See {@link Camera#setDisplayOrientation(int)}.
|
||||||
if (adapter != null && !adapter.isEnabled()) {
|
*/
|
||||||
waitingForBluetooth = true;
|
private int getScreenRotationDegrees() {
|
||||||
eventBus.broadcast(new EnableBluetoothEvent());
|
Display d = getActivity().getWindowManager().getDefaultDisplay();
|
||||||
} else {
|
switch (d.getRotation()) {
|
||||||
startListening();
|
case Surface.ROTATION_0:
|
||||||
|
return 0;
|
||||||
|
case Surface.ROTATION_90:
|
||||||
|
return 90;
|
||||||
|
case Surface.ROTATION_180:
|
||||||
|
return 180;
|
||||||
|
case Surface.ROTATION_270:
|
||||||
|
return 270;
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
stopListening();
|
stopListening();
|
||||||
if (receiver != null) getActivity().unregisterReceiver(receiver);
|
|
||||||
try {
|
try {
|
||||||
cameraView.stop();
|
cameraView.stop();
|
||||||
} catch (CameraException e) {
|
} catch (CameraException e) {
|
||||||
@@ -340,16 +339,4 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
protected void finish() {
|
protected void finish() {
|
||||||
getActivity().getSupportFragmentManager().popBackStack();
|
getActivity().getSupportFragmentManager().popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
|
||||||
@UiThread
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
|
||||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
|
||||||
if (state == STATE_ON && waitingForBluetooth) {
|
|
||||||
waitingForBluetooth = false;
|
|
||||||
startListening();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ import static android.view.View.INVISIBLE;
|
|||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||||
|
|
||||||
public class ChangePasswordActivity extends BaseActivity
|
public class ChangePasswordActivity extends BriarActivity
|
||||||
implements OnClickListener, OnEditorActionListener {
|
implements OnClickListener, OnEditorActionListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
package org.briarproject.briar.android.login;
|
package org.briarproject.briar.android.login;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.login.PowerView.OnCheckedChangedListener;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
@@ -19,12 +21,15 @@ import static android.view.View.VISIBLE;
|
|||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
||||||
|
|
||||||
@TargetApi(23)
|
@NotNullByDefault
|
||||||
public class DozeFragment extends SetupFragment {
|
public class DozeFragment extends SetupFragment
|
||||||
|
implements OnCheckedChangedListener {
|
||||||
|
|
||||||
private final static String TAG = DozeFragment.class.getName();
|
private final static String TAG = DozeFragment.class.getName();
|
||||||
|
|
||||||
private Button dozeButton;
|
private DozeView dozeView;
|
||||||
|
private HuaweiView huaweiView;
|
||||||
|
private Button next;
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private boolean secondAttempt = false;
|
private boolean secondAttempt = false;
|
||||||
|
|
||||||
@@ -33,15 +38,22 @@ public class DozeFragment extends SetupFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater,
|
||||||
Bundle savedInstanceState) {
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
getActivity().setTitle(getString(R.string.setup_doze_title));
|
getActivity().setTitle(getString(R.string.setup_doze_title));
|
||||||
|
setHasOptionsMenu(false);
|
||||||
View v = inflater.inflate(R.layout.fragment_setup_doze, container,
|
View v = inflater.inflate(R.layout.fragment_setup_doze, container,
|
||||||
false);
|
false);
|
||||||
dozeButton = v.findViewById(R.id.dozeButton);
|
dozeView = v.findViewById(R.id.dozeView);
|
||||||
|
dozeView.setOnCheckedChangedListener(this);
|
||||||
|
huaweiView = v.findViewById(R.id.huaweiView);
|
||||||
|
huaweiView.setOnCheckedChangedListener(this);
|
||||||
|
next = v.findViewById(R.id.next);
|
||||||
progressBar = v.findViewById(R.id.progress);
|
progressBar = v.findViewById(R.id.progress);
|
||||||
|
|
||||||
dozeButton.setOnClickListener(view -> askForDozeWhitelisting());
|
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
|
||||||
|
next.setOnClickListener(this);
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
@@ -65,25 +77,34 @@ public class DozeFragment extends SetupFragment {
|
|||||||
public void onActivityResult(int request, int result, Intent data) {
|
public void onActivityResult(int request, int result, Intent data) {
|
||||||
super.onActivityResult(request, result, data);
|
super.onActivityResult(request, result, data);
|
||||||
if (request == REQUEST_DOZE_WHITELISTING) {
|
if (request == REQUEST_DOZE_WHITELISTING) {
|
||||||
if (!setupController.needsDozeWhitelisting() || secondAttempt) {
|
if (!dozeView.needsToBeShown() || secondAttempt) {
|
||||||
dozeButton.setEnabled(false);
|
dozeView.setChecked(true);
|
||||||
onClick(dozeButton);
|
} else if (getContext() != null) {
|
||||||
} else {
|
|
||||||
secondAttempt = true;
|
secondAttempt = true;
|
||||||
showOnboardingDialog(getContext(), getHelpText());
|
showOnboardingDialog(getContext(), getHelpText());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged() {
|
||||||
|
if (dozeView.isChecked() && huaweiView.isChecked()) {
|
||||||
|
next.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
next.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
@SuppressLint("BatteryLife")
|
||||||
private void askForDozeWhitelisting() {
|
private void askForDozeWhitelisting() {
|
||||||
|
if (getContext() == null) return;
|
||||||
Intent i = UiUtils.getDozeWhitelistingIntent(getContext());
|
Intent i = UiUtils.getDozeWhitelistingIntent(getContext());
|
||||||
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
|
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
dozeButton.setVisibility(INVISIBLE);
|
next.setVisibility(INVISIBLE);
|
||||||
progressBar.setVisibility(VISIBLE);
|
progressBar.setVisibility(VISIBLE);
|
||||||
setupController.createAccount();
|
setupController.createAccount();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package org.briarproject.briar.android.login;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
@NotNullByDefault
|
||||||
|
class DozeView extends PowerView {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Runnable onButtonClickListener;
|
||||||
|
|
||||||
|
public DozeView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DozeView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DozeView(Context context, @Nullable AttributeSet attrs,
|
||||||
|
int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
setText(R.string.setup_doze_intro);
|
||||||
|
setButtonText(R.string.setup_doze_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsToBeShown() {
|
||||||
|
return needsToBeShown(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean needsToBeShown(Context context) {
|
||||||
|
return needsDozeWhitelisting(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getHelpText() {
|
||||||
|
return R.string.setup_doze_explanation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onButtonClick() {
|
||||||
|
if (onButtonClickListener == null) throw new IllegalStateException();
|
||||||
|
onButtonClickListener.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnButtonClickListener(Runnable runnable) {
|
||||||
|
onButtonClickListener = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package org.briarproject.briar.android.login;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
@NotNullByDefault
|
||||||
|
class HuaweiView extends PowerView {
|
||||||
|
|
||||||
|
private final static String PACKAGE_NAME = "com.huawei.systemmanager";
|
||||||
|
private final static String CLASS_NAME =
|
||||||
|
PACKAGE_NAME + ".optimize.process.ProtectActivity";
|
||||||
|
|
||||||
|
public HuaweiView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HuaweiView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HuaweiView(Context context, @Nullable AttributeSet attrs,
|
||||||
|
int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
setText(R.string.setup_huawei_text);
|
||||||
|
setButtonText(R.string.setup_huawei_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsToBeShown() {
|
||||||
|
return needsToBeShown(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean needsToBeShown(Context context) {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(getIntent(),
|
||||||
|
PackageManager.MATCH_DEFAULT_ONLY);
|
||||||
|
return !resolveInfos.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@StringRes
|
||||||
|
protected int getHelpText() {
|
||||||
|
return R.string.setup_huawei_help;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onButtonClick() {
|
||||||
|
getContext().startActivity(getIntent());
|
||||||
|
setChecked(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent getIntent() {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setClassName(PACKAGE_NAME, CLASS_NAME);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package org.briarproject.briar.android.login;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public class OpenDatabaseActivity extends BriarActivity
|
||||||
|
implements EventListener {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
LifecycleManager lifecycleManager;
|
||||||
|
@Inject
|
||||||
|
EventBus eventBus;
|
||||||
|
|
||||||
|
private TextView textView;
|
||||||
|
private ImageView imageView;
|
||||||
|
private boolean showingMigration = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
setContentView(R.layout.activity_open_database);
|
||||||
|
textView = findViewById(R.id.textView);
|
||||||
|
imageView = findViewById(R.id.imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
LifecycleState state = lifecycleManager.getLifecycleState();
|
||||||
|
if (state.isAfter(STARTING_SERVICES)) {
|
||||||
|
finishAndStartApp();
|
||||||
|
} else {
|
||||||
|
if (state == MIGRATING_DATABASE) showMigration();
|
||||||
|
eventBus.addListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof LifecycleEvent) {
|
||||||
|
LifecycleState state = ((LifecycleEvent) e).getLifecycleState();
|
||||||
|
if (state.isAfter(STARTING_SERVICES))
|
||||||
|
runOnUiThreadUnlessDestroyed(this::finishAndStartApp);
|
||||||
|
else if (state == MIGRATING_DATABASE)
|
||||||
|
runOnUiThreadUnlessDestroyed(this::showMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMigration() {
|
||||||
|
if (showingMigration) return;
|
||||||
|
textView.setText(R.string.startup_migrate_database);
|
||||||
|
imageView.setImageResource(R.drawable.startup_migration);
|
||||||
|
showingMigration = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishAndStartApp() {
|
||||||
|
startActivity(new Intent(this, NavDrawerActivity.class));
|
||||||
|
supportFinishAfterTransition();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ public class PasswordFragment extends SetupFragment {
|
|||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
|
||||||
// the controller is not yet available in onCreateView()
|
// the controller is not yet available in onCreateView()
|
||||||
if (!setupController.needsDozeWhitelisting()) {
|
if (!setupController.needToShowDozeFragment()) {
|
||||||
nextButton.setText(R.string.create_account_button);
|
nextButton.setText(R.string.create_account_button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ public class PasswordFragment extends SetupFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
if (!setupController.needsDozeWhitelisting()) {
|
if (!setupController.needToShowDozeFragment()) {
|
||||||
nextButton.setVisibility(INVISIBLE);
|
nextButton.setVisibility(INVISIBLE);
|
||||||
progressBar.setVisibility(VISIBLE);
|
progressBar.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package org.briarproject.briar.android.login;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
import android.support.constraint.ConstraintLayout;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
@NotNullByDefault
|
||||||
|
abstract class PowerView extends ConstraintLayout {
|
||||||
|
|
||||||
|
private final TextView textView;
|
||||||
|
private final ImageView checkImage;
|
||||||
|
private final Button button;
|
||||||
|
|
||||||
|
private boolean checked = false;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private OnCheckedChangedListener onCheckedChangedListener;
|
||||||
|
|
||||||
|
public PowerView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PowerView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
public PowerView(Context context, @Nullable AttributeSet attrs,
|
||||||
|
int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
|
||||||
|
LayoutInflater inflater = (LayoutInflater) context
|
||||||
|
.getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||||
|
View v = inflater.inflate(R.layout.power_view, this, true);
|
||||||
|
|
||||||
|
textView = v.findViewById(R.id.textView);
|
||||||
|
checkImage = v.findViewById(R.id.checkImage);
|
||||||
|
button = v.findViewById(R.id.button);
|
||||||
|
button.setOnClickListener(view -> onButtonClick());
|
||||||
|
ImageButton helpButton = v.findViewById(R.id.helpButton);
|
||||||
|
helpButton.setOnClickListener(view -> onHelpButtonClick());
|
||||||
|
|
||||||
|
// we need to manage the checkImage state ourselves, because automatic
|
||||||
|
// state saving is done based on the view's ID and there can be
|
||||||
|
// multiple ImageViews with the same ID in the view hierarchy
|
||||||
|
setSaveFromParentEnabled(true);
|
||||||
|
|
||||||
|
if (!isInEditMode() && !needsToBeShown()) {
|
||||||
|
setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
protected Parcelable onSaveInstanceState() {
|
||||||
|
Parcelable superState = super.onSaveInstanceState();
|
||||||
|
SavedState ss = new SavedState(superState);
|
||||||
|
ss.value = new boolean[] {checked};
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(Parcelable state) {
|
||||||
|
SavedState ss = (SavedState) state;
|
||||||
|
super.onRestoreInstanceState(ss.getSuperState());
|
||||||
|
setChecked(ss.value[0]); // also calls listener
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean needsToBeShown();
|
||||||
|
|
||||||
|
public void setChecked(boolean checked) {
|
||||||
|
this.checked = checked;
|
||||||
|
if (checked) {
|
||||||
|
checkImage.setImageResource(R.drawable.ic_check_white);
|
||||||
|
} else {
|
||||||
|
checkImage.setImageResource(R.drawable.contact_disconnected);
|
||||||
|
}
|
||||||
|
if (onCheckedChangedListener != null) {
|
||||||
|
onCheckedChangedListener.onCheckedChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChecked() {
|
||||||
|
return getVisibility() == GONE || checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnCheckedChangedListener(
|
||||||
|
OnCheckedChangedListener onCheckedChangedListener) {
|
||||||
|
this.onCheckedChangedListener = onCheckedChangedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
protected abstract int getHelpText();
|
||||||
|
|
||||||
|
protected void setText(@StringRes int res) {
|
||||||
|
textView.setText(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setButtonText(@StringRes int res) {
|
||||||
|
button.setText(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void onButtonClick();
|
||||||
|
|
||||||
|
private void onHelpButtonClick() {
|
||||||
|
showOnboardingDialog(getContext(),
|
||||||
|
getContext().getString(getHelpText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SavedState extends BaseSavedState {
|
||||||
|
private boolean[] value = {false};
|
||||||
|
|
||||||
|
private SavedState(@Nullable Parcelable superState) {
|
||||||
|
super(superState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SavedState(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
in.readBooleanArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
|
super.writeToParcel(out, flags);
|
||||||
|
out.writeBooleanArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Parcelable.Creator<SavedState> CREATOR
|
||||||
|
= new Parcelable.Creator<SavedState>() {
|
||||||
|
public SavedState createFromParcel(Parcel in) {
|
||||||
|
return new SavedState(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SavedState[] newArray(int size) {
|
||||||
|
return new SavedState[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnCheckedChangedListener {
|
||||||
|
void onCheckedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ public class SetupActivity extends BaseActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void showApp() {
|
public void showApp() {
|
||||||
Intent i = new Intent(this, NavDrawerActivity.class);
|
Intent i = new Intent(this, OpenDatabaseActivity.class);
|
||||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
supportFinishAfterTransition();
|
supportFinishAfterTransition();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public interface SetupController {
|
|||||||
|
|
||||||
void setSetupActivity(SetupActivity setupActivity);
|
void setSetupActivity(SetupActivity setupActivity);
|
||||||
|
|
||||||
boolean needsDozeWhitelisting();
|
boolean needToShowDozeFragment();
|
||||||
|
|
||||||
void setAuthorName(String authorName);
|
void setAuthorName(String authorName);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.controller.handler.ResultHandler;
|
import org.briarproject.briar.android.controller.handler.ResultHandler;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@@ -41,9 +40,10 @@ public class SetupControllerImpl extends PasswordControllerImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean needsDozeWhitelisting() {
|
public boolean needToShowDozeFragment() {
|
||||||
if (setupActivity == null) throw new IllegalStateException();
|
if (setupActivity == null) throw new IllegalStateException();
|
||||||
return UiUtils.needsDozeWhitelisting(setupActivity);
|
return DozeView.needsToBeShown(setupActivity) ||
|
||||||
|
HuaweiView.needsToBeShown(setupActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -61,7 +61,7 @@ public class SetupControllerImpl extends PasswordControllerImpl
|
|||||||
@Override
|
@Override
|
||||||
public void showDozeOrCreateAccount() {
|
public void showDozeOrCreateAccount() {
|
||||||
if (setupActivity == null) throw new IllegalStateException();
|
if (setupActivity == null) throw new IllegalStateException();
|
||||||
if (needsDozeWhitelisting()) {
|
if (needToShowDozeFragment()) {
|
||||||
setupActivity.showDozeFragment();
|
setupActivity.showDozeFragment();
|
||||||
} else {
|
} else {
|
||||||
createAccount();
|
createAccount();
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import static android.support.v4.view.GravityCompat.START;
|
|||||||
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
|
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
|
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
|
||||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE;
|
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE;
|
||||||
@@ -167,7 +168,7 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void exitIfStartupFailed(Intent intent) {
|
private void exitIfStartupFailed(Intent intent) {
|
||||||
if (intent.getBooleanExtra(KEY_STARTUP_FAILED, false)) {
|
if (intent.getBooleanExtra(EXTRA_STARTUP_FAILED, false)) {
|
||||||
finish();
|
finish();
|
||||||
LOG.info("Exiting");
|
LOG.info("Exiting");
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
@@ -211,13 +212,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (drawerLayout.isDrawerOpen(START)) {
|
if (drawerLayout.isDrawerOpen(START)) {
|
||||||
drawerLayout.closeDrawer(START);
|
drawerLayout.closeDrawer(START);
|
||||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
|
||||||
getSupportFragmentManager()
|
|
||||||
.findFragmentByTag(ContactListFragment.TAG) != null) {
|
|
||||||
Intent i = new Intent(Intent.ACTION_MAIN);
|
|
||||||
i.addCategory(Intent.CATEGORY_HOME);
|
|
||||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
startActivity(i);
|
|
||||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
||||||
getSupportFragmentManager()
|
getSupportFragmentManager()
|
||||||
.findFragmentByTag(ContactListFragment.TAG) == null) {
|
.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user