mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
1 Commits
beta-0.16.
...
831_refact
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bb55d2580 |
@@ -1,20 +0,0 @@
|
||||
image: registry.gitlab.com/fdroid/ci-images-base:latest
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .gradle/wrapper
|
||||
- .gradle/caches
|
||||
|
||||
before_script:
|
||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||
# - export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
|
||||
# - echo y | android --silent update sdk --no-ui --filter android-${ANDROID_COMPILE_SDK}
|
||||
|
||||
test:
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
||||
after_script:
|
||||
# this file changes every time but should not be cached
|
||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
||||
@@ -12,8 +12,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
versionCode 14
|
||||
versionName "0.14"
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
@@ -25,38 +25,38 @@ android {
|
||||
|
||||
dependencies {
|
||||
compile project(':bramble-core')
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
provided 'javax.annotation:jsr250-api:1.0'
|
||||
}
|
||||
|
||||
def torBinaryDir = 'src/main/res/raw'
|
||||
|
||||
task downloadTorGeoIp(type: Download) {
|
||||
src 'https://briarproject.org/build/geoip-2017-05-02.zip'
|
||||
src 'https://briarproject.org/build/geoip-2015-12-01.zip'
|
||||
dest "$torBinaryDir/geoip.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryArm(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-arm.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.7.6-arm.zip'
|
||||
dest "$torBinaryDir/tor_arm.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryArmPie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-arm-pie.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.7.6-arm-pie.zip'
|
||||
dest "$torBinaryDir/tor_arm_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-x86.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.7.6-x86.zip'
|
||||
dest "$torBinaryDir/tor_x86.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86Pie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-x86-pie.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.7.6-x86-pie.zip'
|
||||
dest "$torBinaryDir/tor_x86_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
@@ -64,31 +64,31 @@ task downloadTorBinaryX86Pie(type: Download) {
|
||||
task verifyTorGeoIp(type: Verify, dependsOn: 'downloadTorGeoIp') {
|
||||
src "$torBinaryDir/geoip.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '51f4d1272fb867e1f3b36b67a584e2a33c40b40f62305457d799fd399cd77c9b'
|
||||
checksum '9bcdaf0a7ba0933735328d8ec466c25c25dbb459efc2bce9e55c774eabea5162'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArm(type: Verify, dependsOn: 'downloadTorBinaryArm') {
|
||||
src "$torBinaryDir/tor_arm.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '1da6008663a8ad98b349e62acbbf42c379f65ec504fa467cb119c187cd5a4c6b'
|
||||
checksum '83272962eda701cd5d74d2418651c4ff0f0b1dff51f558a292d1a1c42bf12146'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArmPie(type: Verify, dependsOn: 'downloadTorBinaryArmPie') {
|
||||
src "$torBinaryDir/tor_arm_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'eb061f880829e05f104690ac744848133f2dacef04759d425a2cff0df32c271e'
|
||||
checksum 'd0300d1e45de11ebb24ed62b9c492be9c2e88590b7822195ab38c7a76ffcf646'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86(type: Verify, dependsOn: 'downloadTorBinaryX86') {
|
||||
src "$torBinaryDir/tor_x86.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'f5308aff8303daca082f82227d02b51ddedba4ab1d1420739ada0427ae5dbb41'
|
||||
checksum 'b8813d97b01ee1b9c9a4233c1b9bbe9f9f6b494ae6f9cbd84de8a3911911615e'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86Pie(type: Verify, dependsOn: 'downloadTorBinaryX86Pie') {
|
||||
src "$torBinaryDir/tor_x86_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '889a6c81ac73d05d35ed610ca5a913cee44d333e4ae1749c2a107f2f7dd8197b'
|
||||
checksum '9c66e765aa196dc089951a1b2140cc8290305c2fcbf365121f99e01a233baf4e'
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
|
||||
Binary file not shown.
@@ -19,8 +19,7 @@ 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.AbstractBluetoothPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -75,7 +74,8 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class DroidtoothPlugin implements DuplexPlugin {
|
||||
class DroidtoothPlugin<C, S>
|
||||
extends AbstractBluetoothPlugin<C, S>{
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||
@@ -84,16 +84,10 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
private static final String DISCOVERY_FINISHED =
|
||||
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
|
||||
|
||||
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;
|
||||
@@ -104,29 +98,15 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||
DuplexPluginCallback callback, int maxLatency) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
|
||||
super(ioExecutor, secureRandom, backoff, maxLatency, callback);
|
||||
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;
|
||||
protected void close(S ss) throws IOException {
|
||||
((BluetoothServerSocket)ss).close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -194,14 +174,14 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", getUuid());
|
||||
"RFCOMM", UUID.fromString(getUuid()));
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
tryToClose(ss);
|
||||
tryToClose((S)ss);
|
||||
return;
|
||||
}
|
||||
LOG.info("Socket bound");
|
||||
@@ -213,29 +193,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -261,9 +218,9 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
this.running = false;
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
tryToClose(socket);
|
||||
tryToClose((S)socket);
|
||||
// Disable Bluetooth if we enabled it and it's still enabled
|
||||
if (wasEnabledByUs && adapter.isEnabled()) {
|
||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||
@@ -276,42 +233,20 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
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()) {
|
||||
final ContactId c = e.getKey();
|
||||
if (connected.contains(c)) continue;
|
||||
final String address = e.getValue().get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
final String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
protected Runnable returnPollRunnable(final String address, final String uuid,
|
||||
final ContactId c) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -347,37 +282,17 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
LOG.info("Failed to connect to " + scrubMacAddress(address)
|
||||
+ ": " + e);
|
||||
}
|
||||
tryToClose(s);
|
||||
tryToClose((S)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().get(c);
|
||||
if (p == null) return null;
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
public DuplexTransportConnection connectToAddress(String address, String uuid) {
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s == null) return null;
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
return s == null ? null : wrapSocket(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
@@ -426,7 +341,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
return null;
|
||||
} finally {
|
||||
// Closing the socket will terminate the listener task
|
||||
tryToClose(ss);
|
||||
tryToClose((S)ss);
|
||||
closeSockets(futures, chosen);
|
||||
}
|
||||
}
|
||||
@@ -458,11 +373,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||
if (!isRunning()) return null;
|
||||
@@ -487,31 +397,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
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);
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@@ -523,7 +408,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
bind();
|
||||
} else if (state == STATE_OFF) {
|
||||
LOG.info("Bluetooth disabled");
|
||||
tryToClose(socket);
|
||||
tryToClose((S)socket);
|
||||
}
|
||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||
if (scanMode == SCAN_MODE_NONE) {
|
||||
|
||||
@@ -7,12 +7,12 @@ apply plugin: 'witness'
|
||||
dependencies {
|
||||
compile "com.google.dagger:dagger:2.0.2"
|
||||
compile 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
compile 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
compile 'com.google.code.findbugs:jsr305:3.0.1'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.jmock:jmock:2.8.2"
|
||||
testCompile "org.jmock:jmock-junit4:2.8.2"
|
||||
testCompile "org.jmock:jmock-legacy:2.8.2"
|
||||
testCompile "org.jmock:jmock:2.8.1"
|
||||
testCompile "org.jmock:jmock-junit4:2.8.1"
|
||||
testCompile "org.jmock:jmock-legacy:2.8.1"
|
||||
testCompile "org.hamcrest:hamcrest-library:1.3"
|
||||
testCompile "org.hamcrest:hamcrest-core:1.3"
|
||||
}
|
||||
@@ -21,7 +21,7 @@ dependencyVerification {
|
||||
verify = [
|
||||
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
||||
'com.google.code.findbugs:jsr305:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.code.findbugs:jsr305:c885ce34249682bc0236b4a7d56efcc12048e6135a5baf7a9cde8ad8cda13fcd',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
||||
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
@@ -54,12 +53,6 @@ public class Bytes implements Comparable<Bytes> {
|
||||
return aBytes.length - bBytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() +
|
||||
"(" + StringUtils.toHexString(getBytes()) + ")";
|
||||
}
|
||||
|
||||
public static class BytesComparator implements Comparator<Bytes> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -137,8 +137,7 @@ public interface CryptoComponent {
|
||||
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
|
||||
|
||||
/** Encodes the pseudo-random tag that is used to recognise a stream. */
|
||||
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||
long streamNumber);
|
||||
void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber);
|
||||
|
||||
/**
|
||||
* Signs the given byte[] with the given PrivateKey.
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
package org.briarproject.bramble.api.plugin.duplex;
|
||||
|
||||
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.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
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 java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Created by Santiago Torres-Arias on 1/10/17.
|
||||
*/
|
||||
|
||||
public abstract class AbstractBluetoothPlugin<C, S> implements DuplexPlugin {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger("Halp");
|
||||
|
||||
protected final Executor ioExecutor;
|
||||
protected final SecureRandom secureRandom;
|
||||
protected final Backoff backoff;
|
||||
protected final int maxLatency;
|
||||
protected final DuplexPluginCallback callback;
|
||||
protected final S ss = null;
|
||||
|
||||
protected volatile boolean running = false;
|
||||
|
||||
protected Runnable pollRunnable = null;
|
||||
|
||||
public AbstractBluetoothPlugin(Executor ioExecutor,
|
||||
SecureRandom secureRandom,
|
||||
Backoff backoff, int maxLatency,
|
||||
DuplexPluginCallback callback) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoff = backoff;
|
||||
this.maxLatency = maxLatency;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
return backoff.getPollingInterval();
|
||||
}
|
||||
|
||||
protected String 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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
protected String parseAddress(BdfList descriptor) throws FormatException {
|
||||
byte[] mac = descriptor.getRaw(1);
|
||||
if (mac.length != 6) throw new FormatException();
|
||||
return StringUtils.macToString(mac);
|
||||
}
|
||||
|
||||
protected void tryToClose(@Nullable S ss) {
|
||||
try {
|
||||
if (ss != null) close(ss);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
callback.transportDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void close(S ss) throws IOException;
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
tryToClose(ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(final Collection<ContactId> connected) {
|
||||
if (!running) return;
|
||||
backoff.increment();
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Map.Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
final ContactId c = e.getKey();
|
||||
if (connected.contains(c)) continue;
|
||||
final String address = e.getValue().get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
final String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(returnPollRunnable(address,uuid, c));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Runnable returnPollRunnable(String address, String uuid,
|
||||
ContactId c);
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return null;
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
return connectToAddress(address, uuid);
|
||||
}
|
||||
|
||||
protected abstract DuplexTransportConnection connectToAddress(String address, String uuid);
|
||||
|
||||
@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
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
return connectToAddress(address, uuid);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,11 +4,6 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
|
||||
public interface TransportConstants {
|
||||
|
||||
/**
|
||||
* The current version of the transport protocol.
|
||||
*/
|
||||
int PROTOCOL_VERSION = 3;
|
||||
|
||||
/**
|
||||
* The length of the pseudo-random tag in bytes.
|
||||
*/
|
||||
@@ -19,22 +14,21 @@ public interface TransportConstants {
|
||||
*/
|
||||
int STREAM_HEADER_NONCE_LENGTH = 24;
|
||||
|
||||
/**
|
||||
* The length of the stream header initialisation vector (IV) in bytes.
|
||||
*/
|
||||
int STREAM_HEADER_IV_LENGTH = STREAM_HEADER_NONCE_LENGTH - 8;
|
||||
|
||||
/**
|
||||
* The length of the message authentication code (MAC) in bytes.
|
||||
*/
|
||||
int MAC_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* The length of the stream header plaintext in bytes. The stream header
|
||||
* contains the protocol version, stream number and frame key.
|
||||
*/
|
||||
int STREAM_HEADER_PLAINTEXT_LENGTH = 2 + 8 + SecretKey.LENGTH;
|
||||
|
||||
/**
|
||||
* The length of the stream header in bytes.
|
||||
*/
|
||||
int STREAM_HEADER_LENGTH = STREAM_HEADER_NONCE_LENGTH
|
||||
+ STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH;
|
||||
int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH
|
||||
+ MAC_LENGTH;
|
||||
|
||||
/**
|
||||
* The length of the frame nonce in bytes.
|
||||
|
||||
@@ -19,7 +19,7 @@ public class PrivacyUtils {
|
||||
|
||||
@Nullable
|
||||
public static String scrubMacAddress(@Nullable String address) {
|
||||
if (address == null || address.length() == 0) return null;
|
||||
if (address == null) return null;
|
||||
// this is a fake address we need to know about
|
||||
if (address.equals("02:00:00:00:00:00")) return address;
|
||||
// keep first and last octet of MAC address
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.ltgt.apt' version '0.9'
|
||||
id 'idea'
|
||||
id "java"
|
||||
id "net.ltgt.apt" version "0.9"
|
||||
id "idea"
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
|
||||
@@ -11,18 +10,17 @@ apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
compile project(':bramble-api')
|
||||
compile 'com.madgag.spongycastle:core:1.56.0.0'
|
||||
compile 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
|
||||
compile 'org.bitlet:weupnp:0.1.4'
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
compile 'com.madgag.spongycastle:core:1.54.0.0'
|
||||
compile 'com.h2database:h2:1.4.190'
|
||||
|
||||
testCompile project(path: ':bramble-api', configuration: 'testOutput')
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.madgag.spongycastle:core:5e791b0eaa9e0c4594231b44f616a52adddb7dccedeb0ad9ad74887e19499a23',
|
||||
'com.h2database:h2:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'org.bitlet:weupnp:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'com.madgag.spongycastle:core:1e7fa4b19ccccd1011364ab838d0b4702470c178bbbdd94c5c90b2d4d749ea1e',
|
||||
'com.h2database:h2:23ba495a07bbbb3bd6c3084d10a96dad7a23741b8b6d64b213459a784195a98c'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
BIN
bramble-core/libs/weupnp-0.1.3-SNAPSHOT-briar.jar
Normal file
BIN
bramble-core/libs/weupnp-0.1.3-SNAPSHOT-briar.jar
Normal file
Binary file not shown.
@@ -54,7 +54,6 @@ public class BrambleCoreModule {
|
||||
c.inject(new IdentityModule.EagerSingletons());
|
||||
c.inject(new LifecycleModule.EagerSingletons());
|
||||
c.inject(new PluginModule.EagerSingletons());
|
||||
c.inject(new PropertiesModule.EagerSingletons());
|
||||
c.inject(new SyncModule.EagerSingletons());
|
||||
c.inject(new SystemModule.EagerSingletons());
|
||||
c.inject(new TransportModule.EagerSingletons());
|
||||
|
||||
@@ -45,10 +45,8 @@ import static org.briarproject.bramble.api.invitation.InvitationConstants.CODE_B
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
class CryptoComponentImpl implements CryptoComponent {
|
||||
@@ -414,11 +412,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||
long streamNumber) {
|
||||
public void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber) {
|
||||
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
||||
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
// Initialise the PRF
|
||||
@@ -426,14 +421,10 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
// The output of the PRF must be long enough to use as a tag
|
||||
int macLength = prf.getDigestSize();
|
||||
if (macLength < TAG_LENGTH) throw new IllegalStateException();
|
||||
// The input is the protocol version as a 16-bit integer, followed by
|
||||
// the stream number as a 64-bit integer
|
||||
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
||||
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
||||
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
||||
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
||||
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
||||
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
||||
// The input is the stream number as a 64-bit integer
|
||||
byte[] input = new byte[INT_64_BYTES];
|
||||
ByteUtils.writeUint64(streamNumber, input, 0);
|
||||
prf.update(input, 0, input.length);
|
||||
byte[] mac = new byte[macLength];
|
||||
prf.doFinal(mac, 0);
|
||||
// The output is the first TAG_LENGTH bytes of the MAC
|
||||
|
||||
@@ -20,11 +20,9 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_NO
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@@ -119,7 +117,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
||||
|
||||
private void readStreamHeader() throws IOException {
|
||||
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
|
||||
byte[] streamHeaderPlaintext = new byte[STREAM_HEADER_PLAINTEXT_LENGTH];
|
||||
byte[] streamHeaderPlaintext = new byte[SecretKey.LENGTH];
|
||||
// Read the stream header
|
||||
int offset = 0;
|
||||
while (offset < STREAM_HEADER_LENGTH) {
|
||||
@@ -128,35 +126,21 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
||||
if (read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
// Extract the nonce
|
||||
// The nonce consists of the stream number followed by the IV
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
System.arraycopy(streamHeaderCiphertext, 0, streamHeaderNonce, 0,
|
||||
STREAM_HEADER_NONCE_LENGTH);
|
||||
ByteUtils.writeUint64(streamNumber, streamHeaderNonce, 0);
|
||||
System.arraycopy(streamHeaderCiphertext, 0, streamHeaderNonce,
|
||||
INT_64_BYTES, STREAM_HEADER_IV_LENGTH);
|
||||
// Decrypt and authenticate the stream header
|
||||
try {
|
||||
cipher.init(false, streamHeaderKey, streamHeaderNonce);
|
||||
int decrypted = cipher.process(streamHeaderCiphertext,
|
||||
STREAM_HEADER_NONCE_LENGTH,
|
||||
STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH,
|
||||
STREAM_HEADER_IV_LENGTH, SecretKey.LENGTH + MAC_LENGTH,
|
||||
streamHeaderPlaintext, 0);
|
||||
if (decrypted != STREAM_HEADER_PLAINTEXT_LENGTH)
|
||||
throw new RuntimeException();
|
||||
if (decrypted != SecretKey.LENGTH) throw new RuntimeException();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
// Check the protocol version
|
||||
int receivedProtocolVersion =
|
||||
ByteUtils.readUint16(streamHeaderPlaintext, 0);
|
||||
if (receivedProtocolVersion != PROTOCOL_VERSION)
|
||||
throw new FormatException();
|
||||
// Check the stream number
|
||||
long receivedStreamNumber = ByteUtils.readUint64(streamHeaderPlaintext,
|
||||
INT_16_BYTES);
|
||||
if (receivedStreamNumber != streamNumber) throw new FormatException();
|
||||
// Extract the frame key
|
||||
byte[] frameKeyBytes = new byte[SecretKey.LENGTH];
|
||||
System.arraycopy(streamHeaderPlaintext, INT_16_BYTES + INT_64_BYTES,
|
||||
frameKeyBytes, 0, SecretKey.LENGTH);
|
||||
frameKey = new SecretKey(frameKeyBytes);
|
||||
frameKey = new SecretKey(streamHeaderPlaintext);
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
@Immutable
|
||||
@@ -37,22 +36,22 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
long streamNumber = ctx.getStreamNumber();
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||
crypto.encodeTag(tag, ctx.getTagKey(), streamNumber);
|
||||
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderIv);
|
||||
SecretKey frameKey = crypto.generateSecretKey();
|
||||
return new StreamEncrypterImpl(out, cipher, streamNumber, tag,
|
||||
streamHeaderNonce, ctx.getHeaderKey(), frameKey);
|
||||
streamHeaderIv, ctx.getHeaderKey(), frameKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderIv);
|
||||
SecretKey frameKey = crypto.generateSecretKey();
|
||||
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
|
||||
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderIv,
|
||||
headerKey, frameKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,9 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_NO
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@@ -35,7 +33,7 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
private final long streamNumber;
|
||||
@Nullable
|
||||
private final byte[] tag;
|
||||
private final byte[] streamHeaderNonce;
|
||||
private final byte[] streamHeaderIv;
|
||||
private final byte[] frameNonce, frameHeader;
|
||||
private final byte[] framePlaintext, frameCiphertext;
|
||||
|
||||
@@ -43,13 +41,13 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
private boolean writeTag, writeStreamHeader;
|
||||
|
||||
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher cipher,
|
||||
long streamNumber, @Nullable byte[] tag, byte[] streamHeaderNonce,
|
||||
long streamNumber, @Nullable byte[] tag, byte[] streamHeaderIv,
|
||||
SecretKey streamHeaderKey, SecretKey frameKey) {
|
||||
this.out = out;
|
||||
this.cipher = cipher;
|
||||
this.streamNumber = streamNumber;
|
||||
this.tag = tag;
|
||||
this.streamHeaderNonce = streamHeaderNonce;
|
||||
this.streamHeaderIv = streamHeaderIv;
|
||||
this.streamHeaderKey = streamHeaderKey;
|
||||
this.frameKey = frameKey;
|
||||
frameNonce = new byte[FRAME_NONCE_LENGTH];
|
||||
@@ -116,23 +114,22 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
}
|
||||
|
||||
private void writeStreamHeader() throws IOException {
|
||||
// The header contains the protocol version, stream number and frame key
|
||||
byte[] streamHeaderPlaintext = new byte[STREAM_HEADER_PLAINTEXT_LENGTH];
|
||||
ByteUtils.writeUint16(PROTOCOL_VERSION, streamHeaderPlaintext, 0);
|
||||
ByteUtils.writeUint64(streamNumber, streamHeaderPlaintext,
|
||||
INT_16_BYTES);
|
||||
System.arraycopy(frameKey.getBytes(), 0, streamHeaderPlaintext,
|
||||
INT_16_BYTES + INT_64_BYTES, SecretKey.LENGTH);
|
||||
// The nonce consists of the stream number followed by the IV
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
ByteUtils.writeUint64(streamNumber, streamHeaderNonce, 0);
|
||||
System.arraycopy(streamHeaderIv, 0, streamHeaderNonce, INT_64_BYTES,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
byte[] streamHeaderPlaintext = frameKey.getBytes();
|
||||
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
|
||||
System.arraycopy(streamHeaderNonce, 0, streamHeaderCiphertext, 0,
|
||||
STREAM_HEADER_NONCE_LENGTH);
|
||||
// Encrypt and authenticate the stream header key
|
||||
System.arraycopy(streamHeaderIv, 0, streamHeaderCiphertext, 0,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
// Encrypt and authenticate the frame key
|
||||
try {
|
||||
cipher.init(true, streamHeaderKey, streamHeaderNonce);
|
||||
int encrypted = cipher.process(streamHeaderPlaintext, 0,
|
||||
STREAM_HEADER_PLAINTEXT_LENGTH, streamHeaderCiphertext,
|
||||
STREAM_HEADER_NONCE_LENGTH);
|
||||
if (encrypted != STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH)
|
||||
SecretKey.LENGTH, streamHeaderCiphertext,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
if (encrypted != SecretKey.LENGTH + MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch (GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
|
||||
@@ -70,7 +70,25 @@ class XSalsa20Poly1305AuthenticatedCipher implements AuthenticatedCipher {
|
||||
byte[] subKey = new byte[SUBKEY_LENGTH];
|
||||
xSalsa20Engine.processBytes(zero, 0, SUBKEY_LENGTH, subKey, 0);
|
||||
|
||||
// Clamp the subkey
|
||||
// Reverse the order of the Poly130 subkey
|
||||
//
|
||||
// NaCl and libsodium use the first 32 bytes of XSalsa20 as the
|
||||
// subkey for crypto_onetimeauth_poly1305, which interprets it
|
||||
// as r[0] ... r[15], k[0] ... k[15]. See section 9 of the NaCl
|
||||
// paper (http://cr.yp.to/highspeed/naclcrypto-20090310.pdf),
|
||||
// where the XSalsa20 output is defined as (r, s, t, ...).
|
||||
//
|
||||
// BC's Poly1305 implementation interprets the subkey as
|
||||
// k[0] ... k[15], r[0] ... r[15] (per poly1305_aes_clamp in
|
||||
// the reference implementation).
|
||||
//
|
||||
// To be NaCl-compatible, we reverse the subkey.
|
||||
System.arraycopy(subKey, 0, zero, 0, SUBKEY_LENGTH / 2);
|
||||
System.arraycopy(subKey, SUBKEY_LENGTH / 2, subKey, 0,
|
||||
SUBKEY_LENGTH / 2);
|
||||
System.arraycopy(zero, 0, subKey, SUBKEY_LENGTH / 2,
|
||||
SUBKEY_LENGTH / 2);
|
||||
// Now we can clamp the correct part of the subkey
|
||||
Poly1305KeyGenerator.clamp(subKey);
|
||||
|
||||
// Initialize Poly1305 with the subkey
|
||||
|
||||
@@ -29,7 +29,6 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
@@ -127,8 +126,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
||||
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
||||
streamNumber);
|
||||
crypto.encodeTag(tag, inKeys.getTagKey(), streamNumber);
|
||||
inContexts.put(new Bytes(tag), tagCtx);
|
||||
}
|
||||
}
|
||||
@@ -244,8 +242,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
// Add tags for any stream numbers added to the window
|
||||
for (long streamNumber : change.getAdded()) {
|
||||
byte[] addTag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(addTag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
||||
streamNumber);
|
||||
crypto.encodeTag(addTag, inKeys.getTagKey(), streamNumber);
|
||||
inContexts.put(new Bytes(addTag), new TagContext(
|
||||
tagCtx.contactId, inKeys, streamNumber));
|
||||
}
|
||||
@@ -253,8 +250,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
for (long streamNumber : change.getRemoved()) {
|
||||
if (streamNumber == tagCtx.streamNumber) continue;
|
||||
byte[] removeTag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(removeTag, inKeys.getTagKey(),
|
||||
PROTOCOL_VERSION, streamNumber);
|
||||
crypto.encodeTag(removeTag, inKeys.getTagKey(), streamNumber);
|
||||
inContexts.remove(new Bytes(removeTag));
|
||||
}
|
||||
// Write the window back to the DB
|
||||
|
||||
@@ -14,8 +14,7 @@ import static junit.framework.Assert.assertEquals;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
@@ -23,8 +22,7 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
|
||||
|
||||
private final AuthenticatedCipher cipher;
|
||||
private final SecretKey streamHeaderKey, frameKey;
|
||||
private final byte[] streamHeaderNonce, protocolVersionBytes;
|
||||
private final byte[] streamNumberBytes, payload;
|
||||
private final byte[] streamHeaderIv, payload;
|
||||
private final int payloadLength = 123, paddingLength = 234;
|
||||
private final long streamNumber = 1234;
|
||||
|
||||
@@ -32,12 +30,7 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
|
||||
cipher = new TestAuthenticatedCipher(); // Null cipher
|
||||
streamHeaderKey = TestUtils.getSecretKey();
|
||||
frameKey = TestUtils.getSecretKey();
|
||||
streamHeaderNonce =
|
||||
TestUtils.getRandomBytes(STREAM_HEADER_NONCE_LENGTH);
|
||||
protocolVersionBytes = new byte[2];
|
||||
ByteUtils.writeUint16(PROTOCOL_VERSION, protocolVersionBytes, 0);
|
||||
streamNumberBytes = new byte[8];
|
||||
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
||||
streamHeaderIv = TestUtils.getRandomBytes(STREAM_HEADER_IV_LENGTH);
|
||||
payload = TestUtils.getRandomBytes(payloadLength);
|
||||
}
|
||||
|
||||
@@ -54,9 +47,7 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
|
||||
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(streamHeaderNonce);
|
||||
out.write(protocolVersionBytes);
|
||||
out.write(streamNumberBytes);
|
||||
out.write(streamHeaderIv);
|
||||
out.write(frameKey.getBytes());
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader);
|
||||
@@ -85,85 +76,6 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
|
||||
assertEquals(-1, s.readFrame(buffer));
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testWrongProtocolVersionThrowsException() throws Exception {
|
||||
byte[] wrongProtocolVersionBytes = new byte[2];
|
||||
ByteUtils.writeUint16(PROTOCOL_VERSION + 1, wrongProtocolVersionBytes,
|
||||
0);
|
||||
|
||||
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
|
||||
paddingLength);
|
||||
|
||||
byte[] frameHeader1 = new byte[FRAME_HEADER_LENGTH];
|
||||
int payloadLength1 = 345, paddingLength1 = 456;
|
||||
FrameEncoder.encodeHeader(frameHeader1, true, payloadLength1,
|
||||
paddingLength1);
|
||||
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(streamHeaderNonce);
|
||||
out.write(wrongProtocolVersionBytes);
|
||||
out.write(streamNumberBytes);
|
||||
out.write(frameKey.getBytes());
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader);
|
||||
out.write(payload);
|
||||
out.write(new byte[paddingLength]);
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader1);
|
||||
out.write(payload1);
|
||||
out.write(new byte[paddingLength1]);
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
|
||||
streamNumber, streamHeaderKey);
|
||||
|
||||
// Try to read the first frame
|
||||
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
|
||||
s.readFrame(buffer);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testWrongStreamNumberThrowsException() throws Exception {
|
||||
byte[] wrongStreamNumberBytes = new byte[8];
|
||||
ByteUtils.writeUint64(streamNumber + 1, wrongStreamNumberBytes, 0);
|
||||
|
||||
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
|
||||
paddingLength);
|
||||
|
||||
byte[] frameHeader1 = new byte[FRAME_HEADER_LENGTH];
|
||||
int payloadLength1 = 345, paddingLength1 = 456;
|
||||
FrameEncoder.encodeHeader(frameHeader1, true, payloadLength1,
|
||||
paddingLength1);
|
||||
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(streamHeaderNonce);
|
||||
out.write(protocolVersionBytes);
|
||||
out.write(wrongStreamNumberBytes);
|
||||
out.write(frameKey.getBytes());
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader);
|
||||
out.write(payload);
|
||||
out.write(new byte[paddingLength]);
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader1);
|
||||
out.write(payload1);
|
||||
out.write(new byte[paddingLength1]);
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
|
||||
streamNumber, streamHeaderKey);
|
||||
|
||||
// Try to read the first frame
|
||||
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
|
||||
s.readFrame(buffer);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testTruncatedFrameThrowsException() throws Exception {
|
||||
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -171,9 +83,7 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
|
||||
paddingLength);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(streamHeaderNonce);
|
||||
out.write(protocolVersionBytes);
|
||||
out.write(streamNumberBytes);
|
||||
out.write(streamHeaderIv);
|
||||
out.write(frameKey.getBytes());
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader);
|
||||
@@ -201,9 +111,7 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
|
||||
byte[] payload = TestUtils.getRandomBytes(payloadLength);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(streamHeaderNonce);
|
||||
out.write(protocolVersionBytes);
|
||||
out.write(streamNumberBytes);
|
||||
out.write(streamHeaderIv);
|
||||
out.write(frameKey.getBytes());
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader);
|
||||
@@ -230,9 +138,7 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
|
||||
padding[paddingLength - 1] = 1;
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(streamHeaderNonce);
|
||||
out.write(protocolVersionBytes);
|
||||
out.write(streamNumberBytes);
|
||||
out.write(streamHeaderIv);
|
||||
out.write(frameKey.getBytes());
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader);
|
||||
@@ -256,9 +162,7 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
|
||||
paddingLength);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(streamHeaderNonce);
|
||||
out.write(protocolVersionBytes);
|
||||
out.write(streamNumberBytes);
|
||||
out.write(streamHeaderIv);
|
||||
out.write(frameKey.getBytes());
|
||||
out.write(new byte[MAC_LENGTH]);
|
||||
out.write(frameHeader);
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.crypto;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -12,9 +11,8 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HE
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -23,8 +21,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
|
||||
private final AuthenticatedCipher cipher;
|
||||
private final SecretKey streamHeaderKey, frameKey;
|
||||
private final byte[] tag, streamHeaderNonce, protocolVersionBytes;
|
||||
private final byte[] streamNumberBytes, payload;
|
||||
private final byte[] tag, streamHeaderIv, payload;
|
||||
private final long streamNumber = 1234;
|
||||
private final int payloadLength = 123, paddingLength = 234;
|
||||
|
||||
@@ -33,12 +30,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
streamHeaderKey = TestUtils.getSecretKey();
|
||||
frameKey = TestUtils.getSecretKey();
|
||||
tag = TestUtils.getRandomBytes(TAG_LENGTH);
|
||||
streamHeaderNonce =
|
||||
TestUtils.getRandomBytes(STREAM_HEADER_NONCE_LENGTH);
|
||||
protocolVersionBytes = new byte[2];
|
||||
ByteUtils.writeUint16(PROTOCOL_VERSION, protocolVersionBytes, 0);
|
||||
streamNumberBytes = new byte[8];
|
||||
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
||||
streamHeaderIv = TestUtils.getRandomBytes(STREAM_HEADER_IV_LENGTH);
|
||||
payload = TestUtils.getRandomBytes(payloadLength);
|
||||
}
|
||||
|
||||
@@ -46,8 +38,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testRejectsNegativePayloadLength() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, -1, 0, false);
|
||||
}
|
||||
@@ -56,8 +47,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testRejectsNegativePaddingLength() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, 0, -1, false);
|
||||
}
|
||||
@@ -66,8 +56,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testRejectsMaxPayloadPlusPadding() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH + 1];
|
||||
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 1, false);
|
||||
@@ -77,8 +66,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testAcceptsMaxPayloadIncludingPadding() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
|
||||
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH - 1, 1, false);
|
||||
@@ -90,8 +78,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testAcceptsMaxPayloadWithoutPadding() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
|
||||
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 0, false);
|
||||
@@ -103,17 +90,14 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, payloadLength, 0, false);
|
||||
|
||||
// Expect the tag, stream header, frame header, payload and MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(tag);
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -129,17 +113,14 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWriteUnpaddedFinalFrameWithTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, payloadLength, 0, true);
|
||||
|
||||
// Expect the tag, stream header, frame header, payload and MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(tag);
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -155,16 +136,13 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWriteUnpaddedNonFinalFrameWithoutTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, null, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, payloadLength, 0, false);
|
||||
|
||||
// Expect the stream header, frame header, payload and MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -180,16 +158,13 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWriteUnpaddedFinalFrameWithoutTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, null, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, payloadLength, 0, true);
|
||||
|
||||
// Expect the stream header, frame header, payload and MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -205,17 +180,14 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWritePaddedNonFinalFrameWithTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, payloadLength, paddingLength, false);
|
||||
|
||||
// Expect the tag, stream header, frame header, payload, padding and MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(tag);
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -233,17 +205,14 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWritePaddedFinalFrameWithTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, payloadLength, paddingLength, true);
|
||||
|
||||
// Expect the tag, stream header, frame header, payload, padding and MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(tag);
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -261,16 +230,13 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWritePaddedNonFinalFrameWithoutTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, null, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, payloadLength, paddingLength, false);
|
||||
|
||||
// Expect the stream header, frame header, payload, padding and MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -288,16 +254,13 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWritePaddedFinalFrameWithoutTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, null, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, payloadLength, paddingLength, true);
|
||||
|
||||
// Expect the stream header, frame header, payload, padding and MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -315,8 +278,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testWriteTwoFramesWithTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
int payloadLength1 = 345, paddingLength1 = 456;
|
||||
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
|
||||
|
||||
@@ -327,9 +289,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
// MAC, second frame header, payload, padding, MAC
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(tag);
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
@@ -355,8 +315,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
// Flush the stream once
|
||||
s.flush();
|
||||
@@ -364,9 +323,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
// Expect the tag and stream header
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(tag);
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
|
||||
@@ -378,8 +335,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
// Flush the stream twice
|
||||
s.flush();
|
||||
@@ -388,9 +344,7 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
// Expect the tag and stream header
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(tag);
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
|
||||
@@ -401,17 +355,14 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
public void testFlushDoesNotWriteTagIfNull() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, null, streamHeaderNonce, streamHeaderKey,
|
||||
frameKey);
|
||||
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
// Flush the stream once
|
||||
s.flush();
|
||||
|
||||
// Expect the stream header
|
||||
ByteArrayOutputStream expected = new ByteArrayOutputStream();
|
||||
expected.write(streamHeaderNonce);
|
||||
expected.write(protocolVersionBytes);
|
||||
expected.write(streamNumberBytes);
|
||||
expected.write(streamHeaderIv);
|
||||
expected.write(frameKey.getBytes());
|
||||
expected.write(new byte[MAC_LENGTH]);
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
public class TagEncodingTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final SecretKey tagKey;
|
||||
private final long streamNumber = 1234567890;
|
||||
|
||||
public TagEncodingTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
tagKey = TestUtils.getSecretKey();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyAffectsTag() throws Exception {
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
SecretKey tagKey = TestUtils.getSecretKey();
|
||||
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber);
|
||||
assertTrue(set.add(new Bytes(tag)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtocolVersionAffectsTag() throws Exception {
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
|
||||
assertTrue(set.add(new Bytes(tag)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamNumberAffectsTag() throws Exception {
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);
|
||||
assertTrue(set.add(new Bytes(tag)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import java.util.Collection;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -116,7 +115,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
private void read(byte[] connectionData) throws Exception {
|
||||
// Calculate the expected tag
|
||||
byte[] expectedTag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(expectedTag, tagKey, PROTOCOL_VERSION, streamNumber);
|
||||
crypto.encodeTag(expectedTag, tagKey, streamNumber);
|
||||
|
||||
// Read the tag
|
||||
InputStream in = new ByteArrayInputStream(connectionData);
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
@@ -87,7 +86,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
// Encode the tags (3 sets per contact)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(6).of(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||
with(tagKey), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Save the keys that were rotated
|
||||
@@ -134,7 +133,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||
with(tagKey), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Save the keys
|
||||
@@ -200,7 +199,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||
with(tagKey), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
@@ -248,7 +247,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||
with(tagKey), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
@@ -307,7 +306,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||
with(tagKey), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
@@ -356,7 +355,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||
with(tagKey), with(i));
|
||||
will(new EncodeTagAction(tags));
|
||||
}
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
@@ -366,8 +365,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||
// Encode a new tag after sliding the window
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION),
|
||||
with((long) REORDERING_WINDOW_SIZE));
|
||||
with(tagKey), with((long) REORDERING_WINDOW_SIZE));
|
||||
will(new EncodeTagAction(tags));
|
||||
// Save the reordering window (previous rotation period, base 1)
|
||||
oneOf(db).setReorderingWindow(txn, contactId, transportId, 999,
|
||||
@@ -430,7 +428,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||
with(tagKey), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Schedule key rotation at the start of the next rotation period
|
||||
@@ -452,7 +450,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||
with(tagKey), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Save the keys that were rotated
|
||||
|
||||
@@ -7,19 +7,10 @@ apply plugin: 'witness'
|
||||
dependencies {
|
||||
compile project(':bramble-core')
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
compile 'net.java.dev.jna:jna:4.4.0'
|
||||
compile 'net.java.dev.jna:jna-platform:4.4.0'
|
||||
|
||||
testCompile project(path: ':bramble-core', configuration: 'testOutput')
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'net.java.dev.jna:jna:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1',
|
||||
'net.java.dev.jna:jna-platform:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499',
|
||||
]
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
systemProperty 'java.library.path', 'libs'
|
||||
}
|
||||
|
||||
BIN
bramble-j2se/libs/jna-4.1.0.jar
Normal file
BIN
bramble-j2se/libs/jna-4.1.0.jar
Normal file
Binary file not shown.
BIN
bramble-j2se/libs/jna-platform-4.1.0.jar
Normal file
BIN
bramble-j2se/libs/jna-platform-4.1.0.jar
Normal file
Binary file not shown.
@@ -10,14 +10,14 @@ 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.AbstractBluetoothPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
@@ -57,46 +57,22 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class BluetoothPlugin implements DuplexPlugin {
|
||||
class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Backoff backoff;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final int maxLatency;
|
||||
private final Semaphore discoverySemaphore = new Semaphore(1);
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile StreamConnectionNotifier socket = null;
|
||||
private volatile LocalDevice localDevice = null;
|
||||
|
||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
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;
|
||||
BluetoothPlugin(Executor ioExecutor,
|
||||
SecureRandom secureRandom,
|
||||
Backoff backoff, int maxLatency,
|
||||
DuplexPluginCallback callback) {
|
||||
super(ioExecutor, secureRandom, backoff, maxLatency, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,7 +115,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
tryToClose((S)ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
@@ -153,29 +129,16 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
private String makeUrl(String address, String uuid) {
|
||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||
}
|
||||
|
||||
private String 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;
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable StreamConnectionNotifier 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 tryToClose(@Nullable StreamConnectionNotifier 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(StreamConnectionNotifier ss) {
|
||||
while (true) {
|
||||
@@ -196,54 +159,33 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
//
|
||||
// @Override
|
||||
// public void stop() {
|
||||
// running = false;
|
||||
// tryToClose(socket);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
tryToClose(socket);
|
||||
protected void close(S ss) throws IOException {
|
||||
((StreamConnection)ss).close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
public Runnable returnPollRunnable(final String address, final String uuid,
|
||||
final ContactId c) {
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
return backoff.getPollingInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(final Collection<ContactId> connected) {
|
||||
if (!running) return;
|
||||
backoff.increment();
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
final ContactId c = e.getKey();
|
||||
if (connected.contains(c)) continue;
|
||||
final String address = e.getValue().get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
final String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
StreamConnection s = connect(makeUrl(address, uuid));
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
StreamConnection s = connect(makeUrl(address, uuid));
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private StreamConnection connect(String url) {
|
||||
@@ -259,25 +201,13 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!running) return null;
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return null;
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
String url = makeUrl(address, uuid);
|
||||
protected DuplexTransportConnection connectToAddress(String address, String uuid) {
|
||||
String url = makeUrl(address, uuid);
|
||||
StreamConnection s = connect(url);
|
||||
if (s == null) return null;
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
@@ -297,7 +227,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return null;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
tryToClose((S)ss);
|
||||
return null;
|
||||
}
|
||||
// Create the background tasks
|
||||
@@ -330,7 +260,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return null;
|
||||
} finally {
|
||||
// Closing the socket will terminate the listener task
|
||||
tryToClose(ss);
|
||||
tryToClose((S)ss);
|
||||
closeSockets(futures, chosen);
|
||||
}
|
||||
}
|
||||
@@ -362,11 +292,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||
if (!running) return null;
|
||||
@@ -385,7 +310,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return null;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
tryToClose((S)ss);
|
||||
return null;
|
||||
}
|
||||
BdfList descriptor = new BdfList();
|
||||
@@ -395,33 +320,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
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
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
String url = makeUrl(address, uuid);
|
||||
StreamConnection s = connect(url);
|
||||
if (s == null) return null;
|
||||
return new BluetoothTransportConnection(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);
|
||||
}
|
||||
|
||||
private void makeDeviceDiscoverable() {
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
try {
|
||||
|
||||
@@ -5,10 +5,13 @@ dependencies {
|
||||
def supportVersion = '23.2.1'
|
||||
compile project(':briar-core')
|
||||
compile project(':bramble-android')
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
|
||||
compile "com.android.support:support-v4:$supportVersion"
|
||||
compile("com.android.support:appcompat-v7:$supportVersion") {
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
|
||||
compile("com.android.support:preference-v14:$supportVersion") {
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
@@ -17,7 +20,7 @@ dependencies {
|
||||
exclude module: 'recyclerview-v7'
|
||||
}
|
||||
compile "com.android.support:cardview-v7:$supportVersion"
|
||||
compile "com.android.support:support-annotations:$supportVersion"
|
||||
compile 'com.android.support:support-annotations:23.4.0'
|
||||
compile('ch.acra:acra:4.8.5') {
|
||||
exclude module: 'support-v4'
|
||||
exclude module: 'support-annotations'
|
||||
@@ -25,16 +28,15 @@ dependencies {
|
||||
compile 'info.guardianproject.panic:panic:0.5'
|
||||
compile 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||
compile 'de.hdodenhof:circleimageview:2.1.0'
|
||||
compile 'com.google.zxing:core:3.3.0'
|
||||
compile 'com.jpardogo.materialtabstrip:library:1.1.0'
|
||||
compile 'com.github.bumptech.glide:glide:3.8.0'
|
||||
compile 'uk.co.samuelwall:material-tap-target-prompt:1.9.2'
|
||||
|
||||
compile 'com.google.zxing:core:3.2.1'
|
||||
provided 'javax.annotation:jsr250-api:1.0'
|
||||
compile 'com.jpardogo.materialtabstrip:library:1.1.0'
|
||||
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||
compile 'uk.co.samuelwall:material-tap-target-prompt:1.3.0'
|
||||
|
||||
testCompile project(path: ':bramble-core', configuration: 'testOutput')
|
||||
testCompile 'org.robolectric:robolectric:3.0'
|
||||
testCompile 'org.mockito:mockito-core:2.8.9'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
@@ -43,18 +45,20 @@ dependencyVerification {
|
||||
'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2',
|
||||
'info.guardianproject.trustedintents:trustedintents:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e',
|
||||
'de.hdodenhof:circleimageview:bcbc588e19e6dcf8c120b1957776bfe229efba5d2fbe5da7156372eeacf65503',
|
||||
'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d',
|
||||
'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
|
||||
'com.github.bumptech.glide:glide:750d9e7b940dc0ee48f8680623b55d46e14e8727acc922d7b156e57e7c549655',
|
||||
'uk.co.samuelwall:material-tap-target-prompt:5d4951124366bc5c52e57beaa294db7611f0aa2a8d80e0163e1383e1966ba5b2',
|
||||
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
||||
'com.android.support:support-v4:81ce890f26d35c75ad17d0f998a7e3230330c3b41e0b629566bc744bee89e448',
|
||||
'com.android.support:appcompat-v7:00f9d93acacd6731f309724054bf51492814b4b2869f16d7d5c0038dcb8c9a0d',
|
||||
'com.android.support:preference-v14:44881bb46094e86d0bc2426f205419674a5b4eb514b44b5a4659b5de29f71eb7',
|
||||
'com.android.support:design:003e0c0bea0a6891f8b2bc43f20ae7af2a49a17363e5bb10df5ee0bae12fa686',
|
||||
'com.android.support:cardview-v7:4595f1c4a28cfa083b6c0920ad4d49e1c2ca4b8302a955e548f68eb63b74931b',
|
||||
'com.android.support:support-annotations:e91a88dd0c5e99069b7f09d4a46b5e06f1e9c4c72fc0a8e987e25d86af480f01',
|
||||
'com.android.support:animated-vector-drawable:06d1963b85aa917099d7757e6a7b3e4dc06889413dc747f625ae8683606db3a1',
|
||||
'com.android.support:support-vector-drawable:799bafe4c3de812386f0b291f744d5d6876452722dd40189b9ab87dbbf594ea1',
|
||||
'com.android.support:recyclerview-v7:44040a888e23e0c93162a3377cfe06751080e3c22d369ab0d4301ef60d63b0fe',
|
||||
'com.android.support:preference-v7:775101bd07bd052e455761c5c5d9523d7ad59f2f320e3e8cbde241fd6b1d6025',
|
||||
'com.android.support:cardview-v7:4595f1c4a28cfa083b6c0920ad4d49e1c2ca4b8302a955e548f68eb63b74931b',
|
||||
'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
|
||||
'com.github.bumptech.glide:glide:76ef123957b5fbaebb05fcbe6606dd58c3bc3fcdadb257f99811d0ac9ea9b88b',
|
||||
'uk.co.samuelwall:material-tap-target-prompt:f67e1caead12a914525b32cbf6da52a96b93ff89573f93cb41102ef3130fb64a',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -78,10 +82,7 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
versionCode 1605
|
||||
versionName "0.16.5"
|
||||
applicationId "org.briarproject.briar.beta"
|
||||
resValue "string", "app_package", "org.briarproject.briar.beta"
|
||||
resValue "string", "app_package", "org.briarproject.briar"
|
||||
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
|
||||
}
|
||||
|
||||
@@ -106,6 +107,5 @@ android {
|
||||
lintOptions {
|
||||
warning 'MissingTranslation'
|
||||
warning 'ImpliedQuantity'
|
||||
warning 'ExtraTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
package="org.briarproject.briar"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="13"
|
||||
android:versionName="0.13">
|
||||
|
||||
<uses-feature android:name="android.hardware.bluetooth"/>
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
@@ -19,7 +21,7 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<application
|
||||
android:name="org.briarproject.briar.android.BriarApplicationImpl"
|
||||
android:name=".android.BriarApplicationImpl"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
@@ -27,7 +29,7 @@
|
||||
android:theme="@style/BriarTheme">
|
||||
|
||||
<service
|
||||
android:name="org.briarproject.briar.android.BriarService"
|
||||
android:name=".android.BriarService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="org.briarproject.briar.android.BriarService"/>
|
||||
@@ -35,7 +37,7 @@
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.reporting.DevReportActivity"
|
||||
android:name=".android.reporting.DevReportActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:finishOnTaskLaunch="true"
|
||||
@@ -47,24 +49,24 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
|
||||
android:name=".android.splash.ExpiredActivity"
|
||||
android:label="@string/app_name">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.login.PasswordActivity"
|
||||
android:name=".android.login.PasswordActivity"
|
||||
android:label="@string/app_name"
|
||||
android:windowSoftInputMode="stateVisible">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.login.SetupActivity"
|
||||
android:name=".android.login.SetupActivity"
|
||||
android:label="@string/setup_title"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
|
||||
android:name=".android.splash.SplashScreenActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
@@ -74,268 +76,268 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:name=".android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.contact.ConversationActivity"
|
||||
android:name=".android.contact.ConversationActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
||||
android:name=".android.privategroup.creation.CreateGroupActivity"
|
||||
android:label="@string/groups_create_group_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
||||
android:name=".android.privategroup.conversation.GroupActivity"
|
||||
android:label="@string/app_name"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity"
|
||||
android:name=".android.privategroup.invitation.GroupInvitationActivity"
|
||||
android:label="@string/groups_invitations_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||
android:value=".android.navdrawer.NavDrawerActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity"
|
||||
android:name=".android.privategroup.memberlist.GroupMemberListActivity"
|
||||
android:label="@string/groups_member_list"
|
||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
||||
android:parentActivityName=".android.privategroup.conversation.GroupActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
||||
android:value=".android.privategroup.conversation.GroupActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity"
|
||||
android:name=".android.privategroup.reveal.RevealContactsActivity"
|
||||
android:label="@string/groups_reveal_contacts"
|
||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
||||
android:parentActivityName=".android.privategroup.conversation.GroupActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
||||
android:value=".android.privategroup.conversation.GroupActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.privategroup.creation.GroupInviteActivity"
|
||||
android:name=".android.privategroup.creation.GroupInviteActivity"
|
||||
android:label="@string/groups_invite_members"
|
||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
||||
android:parentActivityName=".android.privategroup.conversation.GroupActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
|
||||
android:value=".android.privategroup.conversation.GroupActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.sharing.ForumInvitationActivity"
|
||||
android:name=".android.sharing.ForumInvitationActivity"
|
||||
android:label="@string/forum_invitations_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.sharing.BlogInvitationActivity"
|
||||
android:name=".android.sharing.BlogInvitationActivity"
|
||||
android:label="@string/blogs_sharing_invitations_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.contact.ConversationActivity">
|
||||
android:parentActivityName=".android.contact.ConversationActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.contact.ConversationActivity"
|
||||
android:value=".android.contact.ConversationActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.forum.CreateForumActivity"
|
||||
android:name=".android.forum.CreateForumActivity"
|
||||
android:label="@string/create_forum_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="stateVisible">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.forum.ForumActivity"
|
||||
android:name=".android.forum.ForumActivity"
|
||||
android:label="@string/app_name"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.sharing.ShareForumActivity"
|
||||
android:name=".android.sharing.ShareForumActivity"
|
||||
android:label="@string/activity_share_toolbar_header"
|
||||
android:parentActivityName="org.briarproject.briar.android.forum.ForumActivity"
|
||||
android:parentActivityName=".android.forum.ForumActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.forum.ForumActivity"
|
||||
android:value=".android.forum.ForumActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.sharing.ShareBlogActivity"
|
||||
android:name=".android.sharing.ShareBlogActivity"
|
||||
android:label="@string/activity_share_toolbar_header"
|
||||
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:parentActivityName=".android.blog.BlogActivity"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:value=".android.blog.BlogActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.sharing.ForumSharingStatusActivity"
|
||||
android:name=".android.sharing.ForumSharingStatusActivity"
|
||||
android:label="@string/sharing_status"
|
||||
android:parentActivityName="org.briarproject.briar.android.forum.ForumActivity">
|
||||
android:parentActivityName=".android.forum.ForumActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.forum.ForumActivity"
|
||||
android:value=".android.forum.ForumActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.sharing.BlogSharingStatusActivity"
|
||||
android:name=".android.sharing.BlogSharingStatusActivity"
|
||||
android:label="@string/sharing_status"
|
||||
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity">
|
||||
android:parentActivityName=".android.blog.BlogActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:value=".android.blog.BlogActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:name=".android.blog.BlogActivity"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||
android:value=".android.navdrawer.NavDrawerActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.blog.WriteBlogPostActivity"
|
||||
android:name=".android.blog.WriteBlogPostActivity"
|
||||
android:label="@string/blogs_write_blog_post"
|
||||
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:parentActivityName=".android.blog.BlogActivity"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:value=".android.blog.BlogActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.blog.ReblogActivity"
|
||||
android:name=".android.blog.ReblogActivity"
|
||||
android:label="@string/blogs_reblog_button"
|
||||
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:parentActivityName=".android.blog.BlogActivity"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.blog.BlogActivity"
|
||||
android:value=".android.blog.BlogActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.blog.RssFeedImportActivity"
|
||||
android:name=".android.blog.RssFeedImportActivity"
|
||||
android:label="@string/blogs_rss_feeds_import"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.blog.RssFeedManageActivity"
|
||||
android:name=".android.blog.RssFeedManageActivity"
|
||||
android:label="@string/blogs_rss_feeds_manage"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.invitation.AddContactActivity"
|
||||
android:name=".android.invitation.AddContactActivity"
|
||||
android:label="@string/add_contact_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
|
||||
android:name=".android.keyagreement.KeyAgreementActivity"
|
||||
android:label="@string/add_contact_title"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||
android:value=".android.navdrawer.NavDrawerActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.introduction.IntroductionActivity"
|
||||
android:name=".android.introduction.IntroductionActivity"
|
||||
android:label="@string/introduction_activity_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.contact.ConversationActivity"
|
||||
android:parentActivityName=".android.contact.ConversationActivity"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.contact.ConversationActivity"
|
||||
android:value=".android.contact.ConversationActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.StartupFailureActivity"
|
||||
android:name=".android.StartupFailureActivity"
|
||||
android:label="@string/startup_failed_activity_title">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.settings.SettingsActivity"
|
||||
android:name=".android.settings.SettingsActivity"
|
||||
android:label="@string/settings_button"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:permission="android.permission.READ_NETWORK_USAGE_HISTORY">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE"/>
|
||||
@@ -344,27 +346,27 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.login.ChangePasswordActivity"
|
||||
android:name=".android.login.ChangePasswordActivity"
|
||||
android:label="@string/change_password"
|
||||
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
|
||||
android:parentActivityName=".android.settings.SettingsActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.settings.SettingsActivity"
|
||||
android:value=".android.settings.SettingsActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.panic.PanicPreferencesActivity"
|
||||
android:name=".android.panic.PanicPreferencesActivity"
|
||||
android:label="@string/panic_setting"
|
||||
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
|
||||
android:parentActivityName=".android.settings.SettingsActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.settings.SettingsActivity"
|
||||
android:value=".android.settings.SettingsActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.panic.PanicResponderActivity"
|
||||
android:name=".android.panic.PanicResponderActivity"
|
||||
android:noHistory="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<!-- this can never have launchMode singleTask or singleInstance! -->
|
||||
@@ -375,7 +377,7 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.panic.ExitActivity"
|
||||
android:name=".android.panic.ExitActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.feed.FeedManager;
|
||||
import org.briarproject.briar.api.forum.ForumManager;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
@@ -79,8 +78,6 @@ public interface AndroidComponent
|
||||
@DatabaseExecutor
|
||||
Executor databaseExecutor();
|
||||
|
||||
MessageTracker messageTracker();
|
||||
|
||||
LifecycleManager lifecycleManager();
|
||||
|
||||
IdentityManager identityManager();
|
||||
|
||||
@@ -61,7 +61,6 @@ import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
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.VISIBILITY_PRIVATE;
|
||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
@@ -328,12 +327,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_MESSAGE);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
@@ -353,6 +347,17 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
return defaults;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllContactNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
clearContactNotification();
|
||||
clearIntroductionSuccessNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showGroupMessageNotification(final GroupId g) {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@@ -427,12 +432,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_SOCIAL);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
@@ -440,6 +440,16 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllGroupMessageNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
clearGroupMessageNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showForumPostNotification(final GroupId g) {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@@ -514,12 +524,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_SOCIAL);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
@@ -527,6 +532,16 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllForumPostNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
clearForumPostNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showBlogPostNotification(final GroupId g) {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@@ -587,12 +602,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_SOCIAL);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
@@ -648,12 +658,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_MESSAGE);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
@@ -700,6 +705,68 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockAllContactNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
blockContacts = true;
|
||||
blockIntroductions = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unblockAllContactNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
blockContacts = false;
|
||||
blockIntroductions = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockAllGroupMessageNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
blockGroups = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unblockAllGroupMessageNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
blockGroups = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockAllForumPostNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
blockForums = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unblockAllForumPostNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
blockForums = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockAllBlogPostNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@@ -719,4 +786,5 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ public class AppModule {
|
||||
static class EagerSingletons {
|
||||
@Inject
|
||||
AndroidNotificationManager androidNotificationManager;
|
||||
@Inject
|
||||
ScreenFilterMonitor screenFilterMonitor;
|
||||
}
|
||||
|
||||
private final Application application;
|
||||
@@ -169,8 +171,10 @@ public class AppModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ScreenFilterMonitor provideScreenFilterMonitor(
|
||||
ScreenFilterMonitorImpl screenFilterMonitor) {
|
||||
return screenFilterMonitor;
|
||||
LifecycleManager lifecycleManager, ScreenFilterMonitorImpl sfm) {
|
||||
lifecycleManager.registerService(sfm);
|
||||
return sfm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,5 @@ package org.briarproject.briar.android;
|
||||
*/
|
||||
public interface BriarApplication {
|
||||
|
||||
// This build expires on 21 October 2017
|
||||
long EXPIRY_DATE = 1508544000 * 1000L;
|
||||
|
||||
AndroidComponent getApplicationComponent();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@ package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.StrictMode;
|
||||
import android.os.StrictMode.ThreadPolicy;
|
||||
import android.os.StrictMode.VmPolicy;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.ReportingInteractionMode;
|
||||
@@ -36,8 +33,6 @@ import static org.acra.ReportField.REPORT_ID;
|
||||
import static org.acra.ReportField.STACK_TRACE;
|
||||
import static org.acra.ReportField.USER_APP_START_DATE;
|
||||
import static org.acra.ReportField.USER_CRASH_DATE;
|
||||
import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
|
||||
@ReportsCrashes(
|
||||
reportPrimerClass = BriarReportPrimer.class,
|
||||
@@ -77,9 +72,6 @@ public class BriarApplicationImpl extends Application
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (IS_DEBUG_BUILD) enableStrictMode();
|
||||
Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
|
||||
LOG.info("Created");
|
||||
|
||||
applicationComponent = DaggerAndroidComponent.builder()
|
||||
@@ -93,17 +85,6 @@ public class BriarApplicationImpl extends Application
|
||||
AndroidEagerSingletons.initEagerSingletons(applicationComponent);
|
||||
}
|
||||
|
||||
private void enableStrictMode() {
|
||||
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
|
||||
threadPolicy.detectAll();
|
||||
threadPolicy.penaltyLog();
|
||||
StrictMode.setThreadPolicy(threadPolicy.build());
|
||||
VmPolicy.Builder vmPolicy = new VmPolicy.Builder();
|
||||
vmPolicy.detectAll();
|
||||
vmPolicy.penaltyLog();
|
||||
StrictMode.setVmPolicy(vmPolicy.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidComponent getApplicationComponent() {
|
||||
return applicationComponent;
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
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.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.Signature;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
@@ -16,26 +27,37 @@ import java.io.InputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
||||
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||
import static android.content.Intent.EXTRA_REPLACING;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||
import static android.content.pm.PackageManager.GET_SIGNATURES;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@NotNullByDefault
|
||||
class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
||||
implements Service, ScreenFilterMonitor {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
||||
private static final String PREF_SCREEN_FILTER_APPS =
|
||||
"shownScreenFilterApps";
|
||||
|
||||
/*
|
||||
* Ignore Play Services if it uses this package name and public key - it's
|
||||
@@ -56,17 +78,124 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
||||
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
|
||||
"0B145B6AA192858E79020103";
|
||||
|
||||
private final Context appContext;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final PackageManager pm;
|
||||
private final SharedPreferences prefs;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
// The following must only be accessed on the UI thread
|
||||
private final Set<String> apps = new HashSet<>();
|
||||
private final Set<String> shownApps;
|
||||
private boolean serviceStarted = false;
|
||||
|
||||
@Inject
|
||||
ScreenFilterMonitorImpl(Application app) {
|
||||
pm = app.getPackageManager();
|
||||
ScreenFilterMonitorImpl(AndroidExecutor executor, Application app) {
|
||||
this.androidExecutor = executor;
|
||||
this.appContext = app;
|
||||
pm = appContext.getPackageManager();
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
shownApps = getShownScreenFilterApps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(ACTION_PACKAGE_ADDED);
|
||||
intentFilter.addDataScheme("package");
|
||||
appContext.registerReceiver(ScreenFilterMonitorImpl.this,
|
||||
intentFilter);
|
||||
apps.addAll(getInstalledScreenFilterApps());
|
||||
serviceStarted = true;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() throws ServiceException {
|
||||
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() {
|
||||
serviceStarted = false;
|
||||
appContext.unregisterReceiver(ScreenFilterMonitorImpl.this);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getShownScreenFilterApps() {
|
||||
// Result must not be modified
|
||||
Set<String> s = prefs.getStringSet(PREF_SCREEN_FILTER_APPS, null);
|
||||
HashSet<String> result = new HashSet<>();
|
||||
if (s != null) {
|
||||
result.addAll(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
|
||||
final String packageName =
|
||||
intent.getData().getEncodedSchemeSpecificPart();
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String pkg = isOverlayApp(packageName);
|
||||
if (pkg == null) {
|
||||
return;
|
||||
}
|
||||
apps.add(pkg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public Set<String> getApps() {
|
||||
Set<String> screenFilterApps = new TreeSet<>();
|
||||
if (!serviceStarted) {
|
||||
apps.addAll(getInstalledScreenFilterApps());
|
||||
}
|
||||
TreeSet<String> buf = new TreeSet<>();
|
||||
if (apps.isEmpty()) {
|
||||
return buf;
|
||||
}
|
||||
buf.addAll(apps);
|
||||
buf.removeAll(shownApps);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void storeAppsAsShown(Collection<String> s, boolean persistent) {
|
||||
HashSet<String> buf = new HashSet<>(s);
|
||||
shownApps.addAll(buf);
|
||||
if (persistent && !s.isEmpty()) {
|
||||
buf.addAll(getShownScreenFilterApps());
|
||||
prefs.edit()
|
||||
.putStringSet(PREF_SCREEN_FILTER_APPS, buf)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getInstalledScreenFilterApps() {
|
||||
HashSet<String> screenFilterApps = new HashSet<>();
|
||||
List<PackageInfo> packageInfos =
|
||||
pm.getInstalledPackages(GET_PERMISSIONS);
|
||||
for (PackageInfo packageInfo : packageInfos) {
|
||||
@@ -80,6 +209,21 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
||||
return screenFilterApps;
|
||||
}
|
||||
|
||||
// Checks if a package uses the SYSTEM_ALERT_WINDOW permission and if so
|
||||
// returns the app name.
|
||||
@Nullable
|
||||
private String isOverlayApp(String pkg) {
|
||||
try {
|
||||
PackageInfo pkgInfo = pm.getPackageInfo(pkg, GET_PERMISSIONS);
|
||||
if (isOverlayApp(pkgInfo)) {
|
||||
return pkgToString(pkgInfo);
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetches the application name for a given package.
|
||||
@Nullable
|
||||
private String pkgToString(PackageInfo pkgInfo) {
|
||||
|
||||
@@ -10,21 +10,13 @@ import static java.util.logging.Level.OFF;
|
||||
public interface TestingConstants {
|
||||
|
||||
/**
|
||||
* Whether this is a debug build.
|
||||
*/
|
||||
boolean IS_DEBUG_BUILD = BuildConfig.DEBUG;
|
||||
|
||||
/**
|
||||
* Whether this is a beta build. This should be set to false for final
|
||||
* Whether this is an alpha or beta build. This should be set to false for
|
||||
* release builds.
|
||||
*/
|
||||
boolean IS_BETA_BUILD = true;
|
||||
boolean TESTING = BuildConfig.DEBUG;
|
||||
|
||||
/**
|
||||
* Default log level. Disable logging for final release builds.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
Level DEFAULT_LOG_LEVEL = IS_DEBUG_BUILD || IS_BETA_BUILD ? INFO : OFF;
|
||||
/** Default log level. */
|
||||
Level DEFAULT_LOG_LEVEL = TESTING ? INFO : OFF;
|
||||
|
||||
/**
|
||||
* Whether to prevent screenshots from being taken. Setting this to true
|
||||
@@ -32,5 +24,5 @@ public interface TestingConstants {
|
||||
* Unfortunately this also prevents the user from taking screenshots
|
||||
* intentionally.
|
||||
*/
|
||||
boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD;
|
||||
boolean PREVENT_SCREENSHOTS = !TESTING;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.briarproject.briar.android.privategroup.conversation.GroupConversatio
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupMessageFragment;
|
||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupModule;
|
||||
import org.briarproject.briar.android.privategroup.creation.GroupCreateModule;
|
||||
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
|
||||
import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
|
||||
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
|
||||
@@ -71,7 +71,7 @@ import dagger.Component;
|
||||
@Component(
|
||||
modules = {ActivityModule.class, ForumModule.class, SharingModule.class,
|
||||
BlogModule.class, ContactModule.class, GroupListModule.class,
|
||||
CreateGroupModule.class, GroupInvitationModule.class,
|
||||
GroupCreateModule.class, GroupInvitationModule.class,
|
||||
GroupConversationModule.class, GroupMemberModule.class,
|
||||
GroupRevealModule.class},
|
||||
dependencies = AndroidComponent.class)
|
||||
|
||||
@@ -2,13 +2,9 @@ package org.briarproject.briar.android.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -17,9 +13,7 @@ import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
|
||||
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
|
||||
import org.briarproject.briar.android.fragment.SFDialogFragment;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -29,23 +23,21 @@ import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
||||
|
||||
public abstract class BaseActivity extends AppCompatActivity
|
||||
implements DestroyableContext, OnTapFilteredListener {
|
||||
|
||||
@Inject
|
||||
protected ScreenFilterMonitor screenFilterMonitor;
|
||||
|
||||
implements DestroyableContext {
|
||||
protected ActivityComponent activityComponent;
|
||||
|
||||
private final List<ActivityLifecycleController> lifecycleControllers =
|
||||
new ArrayList<>();
|
||||
private boolean destroyed = false;
|
||||
private ScreenFilterDialogFragment dialogFrag;
|
||||
|
||||
@Inject
|
||||
protected ScreenFilterMonitor screenFilterMonitor;
|
||||
private SFDialogFragment dialogFrag;
|
||||
|
||||
public abstract void injectActivity(ActivityComponent component);
|
||||
|
||||
@@ -73,6 +65,7 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||
alc.onActivityCreate(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ActivityComponent getActivityComponent() {
|
||||
@@ -104,6 +97,12 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
showNewScreenFilterWarning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
@@ -113,14 +112,18 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void showScreenFilterWarning() {
|
||||
if (dialogFrag != null && dialogFrag.isVisible()) return;
|
||||
Set<String> apps = screenFilterMonitor.getApps();
|
||||
if (apps.isEmpty()) return;
|
||||
dialogFrag =
|
||||
ScreenFilterDialogFragment.newInstance(new ArrayList<>(apps));
|
||||
protected void showNewScreenFilterWarning() {
|
||||
final Set<String> apps = screenFilterMonitor.getApps();
|
||||
if (apps.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
dialogFrag = SFDialogFragment.newInstance(new ArrayList<>(apps));
|
||||
dialogFrag.setCancelable(false);
|
||||
dialogFrag.show(getSupportFragmentManager(), dialogFrag.getTag());
|
||||
dialogFrag.show(getSupportFragmentManager(), "SFDialog");
|
||||
}
|
||||
|
||||
public void rememberShownApps(ArrayList<String> s, boolean permanent) {
|
||||
screenFilterMonitor.storeAppsAsShown(s, permanent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,70 +161,4 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
/*
|
||||
* Wraps the given view in a wrapper that notifies this activity when an
|
||||
* obscured touch has been filtered, and returns the wrapper.
|
||||
*/
|
||||
private View makeTapSafeWrapper(View v) {
|
||||
TapSafeFrameLayout wrapper = new TapSafeFrameLayout(this);
|
||||
wrapper.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
wrapper.setOnTapFilteredListener(this);
|
||||
wrapper.addView(v);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds the AppCompat toolbar, if any, and configures it to filter
|
||||
* obscured touches. If a custom toolbar is used, it will be part of the
|
||||
* content view and thus protected by the wrapper. But the default toolbar
|
||||
* is outside the wrapper.
|
||||
*/
|
||||
private void protectToolbar() {
|
||||
View decorView = getWindow().getDecorView();
|
||||
if (decorView instanceof ViewGroup) {
|
||||
Toolbar toolbar = findToolbar((ViewGroup) decorView);
|
||||
if (toolbar != null) toolbar.setFilterTouchesWhenObscured(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Toolbar findToolbar(ViewGroup vg) {
|
||||
for (int i = 0, len = vg.getChildCount(); i < len; i++) {
|
||||
View child = vg.getChildAt(i);
|
||||
if (child instanceof Toolbar) return (Toolbar) child;
|
||||
if (child instanceof ViewGroup) {
|
||||
Toolbar toolbar = findToolbar((ViewGroup) child);
|
||||
if (toolbar != null) return toolbar;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentView(@LayoutRes int layoutRes) {
|
||||
setContentView(getLayoutInflater().inflate(layoutRes, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentView(View v) {
|
||||
super.setContentView(makeTapSafeWrapper(v));
|
||||
protectToolbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentView(View v, LayoutParams layoutParams) {
|
||||
super.setContentView(makeTapSafeWrapper(v), layoutParams);
|
||||
protectToolbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addContentView(View v, LayoutParams layoutParams) {
|
||||
super.addContentView(makeTapSafeWrapper(v), layoutParams);
|
||||
protectToolbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTapFiltered() {
|
||||
showScreenFilterWarning();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.activity;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.transition.Slide;
|
||||
@@ -20,7 +21,6 @@ import org.briarproject.briar.android.panic.ExitActivity;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -21,10 +20,8 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
||||
|
||||
@UiThread
|
||||
@@ -60,20 +57,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
false);
|
||||
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
ui = new BlogPostViewHolder(view, true, new OnBlogPostClickListener() {
|
||||
@Override
|
||||
public void onBlogPostClick(BlogPostItem post) {
|
||||
// We're already there
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthorClick(BlogPostItem post) {
|
||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
getContext().startActivity(i);
|
||||
}
|
||||
});
|
||||
ui = new BlogPostViewHolder(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
|
||||
@@ -14,7 +15,6 @@ import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
|
||||
@@ -106,7 +106,7 @@ class BlogControllerImpl extends BaseControllerImpl
|
||||
BlogInvitationResponseReceivedEvent b =
|
||||
(BlogInvitationResponseReceivedEvent) e;
|
||||
InvitationResponse r = b.getResponse();
|
||||
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
|
||||
if (r.getGroupId().equals(groupId) && r.wasAccepted()) {
|
||||
LOG.info("Blog invitation accepted");
|
||||
onBlogInvitationAccepted(b.getContactId());
|
||||
}
|
||||
@@ -169,7 +169,7 @@ class BlogControllerImpl extends BaseControllerImpl
|
||||
LocalAuthor a = identityManager.getLocalAuthor();
|
||||
Blog b = blogManager.getBlog(groupId);
|
||||
boolean ours = a.getId().equals(b.getAuthor().getId());
|
||||
boolean removable = blogManager.canBeRemoved(b);
|
||||
boolean removable = blogManager.canBeRemoved(groupId);
|
||||
BlogItem blog = new BlogItem(b, ours, removable);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.blog.BlogController.BlogSharingListener;
|
||||
import org.briarproject.briar.android.blog.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.briar.android.controller.SharingController;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
@@ -215,19 +216,10 @@ public class BlogFragment extends BaseFragment
|
||||
showNextFragment(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthorClick(BlogPostItem post) {
|
||||
if (post.getGroupId().equals(groupId)) return; // We're already there
|
||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
getContext().startActivity(i);
|
||||
}
|
||||
|
||||
private void loadBlogPosts(final boolean reload) {
|
||||
blogController.loadBlogPosts(
|
||||
new UiResultExceptionHandler<Collection<BlogPostItem>,
|
||||
DbException>(this) {
|
||||
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
|
||||
this) {
|
||||
@Override
|
||||
public void onResultUi(Collection<BlogPostItem> posts) {
|
||||
if (posts.isEmpty()) {
|
||||
@@ -265,13 +257,13 @@ public class BlogFragment extends BaseFragment
|
||||
}
|
||||
|
||||
private void setToolbarTitle(Author a) {
|
||||
getActivity().setTitle(a.getName());
|
||||
String title = getString(R.string.blogs_personal_blog, a.getName());
|
||||
getActivity().setTitle(title);
|
||||
}
|
||||
|
||||
private void loadSharedContacts() {
|
||||
blogController.loadSharingContacts(
|
||||
new UiResultExceptionHandler<Collection<ContactId>,
|
||||
DbException>(this) {
|
||||
new UiResultExceptionHandler<Collection<ContactId>, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(Collection<ContactId> contacts) {
|
||||
sharingController.addAll(contacts);
|
||||
|
||||
@@ -23,7 +23,8 @@ class BlogPostAdapter
|
||||
int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
R.layout.list_item_blog_post, parent, false);
|
||||
BlogPostViewHolder ui = new BlogPostViewHolder(v, false, listener);
|
||||
BlogPostViewHolder ui = new BlogPostViewHolder(v);
|
||||
ui.setOnBlogPostClickListener(listener);
|
||||
return ui;
|
||||
}
|
||||
|
||||
@@ -47,4 +48,8 @@ class BlogPostAdapter
|
||||
return a.getId().equals(b.getId());
|
||||
}
|
||||
|
||||
interface OnBlogPostClickListener {
|
||||
void onBlogPostClick(BlogPostItem post);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,16 +8,14 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.blog.BaseController.BlogListener;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BlogPostFragment extends BasePostFragment implements BlogListener {
|
||||
public class BlogPostFragment extends BasePostFragment {
|
||||
|
||||
private static final String TAG = BlogPostFragment.class.getName();
|
||||
|
||||
@@ -42,7 +40,6 @@ public class BlogPostFragment extends BasePostFragment implements BlogListener {
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
blogController.setBlogListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,15 +59,4 @@ public class BlogPostFragment extends BasePostFragment implements BlogListener {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
|
||||
// doesn't matter here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogRemoved() {
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
@@ -21,6 +20,7 @@ import android.widget.TextView;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.blog.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.briar.android.view.AuthorView;
|
||||
import org.briarproject.briar.api.blog.BlogCommentHeader;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
@@ -48,16 +48,11 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
private final ImageView reblogButton;
|
||||
private final TextView body;
|
||||
private final ViewGroup commentContainer;
|
||||
private final boolean fullText;
|
||||
|
||||
@NonNull
|
||||
private final OnBlogPostClickListener listener;
|
||||
private OnBlogPostClickListener listener;
|
||||
|
||||
BlogPostViewHolder(View v, boolean fullText,
|
||||
@NonNull OnBlogPostClickListener listener) {
|
||||
BlogPostViewHolder(View v) {
|
||||
super(v);
|
||||
this.fullText = fullText;
|
||||
this.listener = listener;
|
||||
|
||||
ctx = v.getContext();
|
||||
layout = (ViewGroup) v.findViewById(R.id.postLayout);
|
||||
@@ -69,6 +64,10 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
(ViewGroup) v.findViewById(R.id.commentContainer);
|
||||
}
|
||||
|
||||
void setOnBlogPostClickListener(OnBlogPostClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
void setVisibility(int visibility) {
|
||||
layout.setVisibility(visibility);
|
||||
}
|
||||
@@ -93,7 +92,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
if (item == null) return;
|
||||
|
||||
setTransitionName(item.getId());
|
||||
if (!fullText) {
|
||||
if (listener != null) {
|
||||
layout.setClickable(true);
|
||||
layout.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@@ -112,20 +111,15 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
author.setPersona(
|
||||
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
|
||||
// TODO make author clickable more often #624
|
||||
if (!fullText && item.getHeader().getType() == POST) {
|
||||
author.setAuthorClickable(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onAuthorClick(item);
|
||||
}
|
||||
});
|
||||
if (item.getHeader().getType() == POST) {
|
||||
author.setBlogLink(post.getGroupId());
|
||||
} else {
|
||||
author.setAuthorNotClickable();
|
||||
author.unsetBlogLink();
|
||||
}
|
||||
|
||||
// post body
|
||||
Spanned bodyText = getSpanned(item.getBody());
|
||||
if (fullText) {
|
||||
if (listener == null) {
|
||||
body.setText(bodyText);
|
||||
body.setTextIsSelectable(true);
|
||||
makeLinksClickable(body);
|
||||
@@ -171,14 +165,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
reblogger.setAuthor(item.getAuthor());
|
||||
reblogger.setAuthorStatus(item.getAuthorStatus());
|
||||
reblogger.setDate(item.getTimestamp());
|
||||
if (!fullText) {
|
||||
reblogger.setAuthorClickable(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onAuthorClick(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
reblogger.setBlogLink(item.getGroupId());
|
||||
reblogger.setVisibility(VISIBLE);
|
||||
reblogger.setPersona(AuthorView.REBLOGGER);
|
||||
|
||||
@@ -201,7 +188,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
// TODO make author clickable #624
|
||||
|
||||
body.setText(c.getComment());
|
||||
if (fullText) body.setTextIsSelectable(true);
|
||||
if (listener == null) body.setTextIsSelectable(true);
|
||||
|
||||
commentContainer.addView(v);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ 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;
|
||||
import org.briarproject.briar.android.blog.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.briar.android.blog.FeedController.FeedListener;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
@@ -33,7 +34,6 @@ import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
||||
@@ -223,14 +223,6 @@ public class FeedFragment extends BaseFragment implements
|
||||
showNextFragment(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthorClick(BlogPostItem post) {
|
||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
getContext().startActivity(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
interface OnBlogPostClickListener {
|
||||
|
||||
void onBlogPostClick(BlogPostItem post);
|
||||
|
||||
void onAuthorClick(BlogPostItem post);
|
||||
}
|
||||
@@ -161,18 +161,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
||||
private ViewHolder(View v) {
|
||||
scrollView = (ScrollView) v.findViewById(R.id.scrollView);
|
||||
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
|
||||
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
|
||||
true, new OnBlogPostClickListener() {
|
||||
@Override
|
||||
public void onBlogPostClick(BlogPostItem post) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthorClick(BlogPostItem post) {
|
||||
// probably don't want to allow author clicks here
|
||||
}
|
||||
});
|
||||
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout));
|
||||
input = (TextInputView) v.findViewById(R.id.inputText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -21,12 +20,9 @@ import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.api.feed.FeedManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
@@ -102,17 +98,10 @@ public class RssFeedImportActivity extends BriarActivity {
|
||||
|
||||
private void enableOrDisableImportButton() {
|
||||
String url = urlInput.getText().toString();
|
||||
importButton.setEnabled(validateAndNormaliseUrl(url) != null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String validateAndNormaliseUrl(String url) {
|
||||
if (!Patterns.WEB_URL.matcher(url).matches()) return null;
|
||||
try {
|
||||
return new URL(url).toString();
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
if (url.startsWith("http://") || url.startsWith("https://"))
|
||||
importButton.setEnabled(true);
|
||||
else
|
||||
importButton.setEnabled(false);
|
||||
}
|
||||
|
||||
private void publish() {
|
||||
@@ -120,9 +109,7 @@ public class RssFeedImportActivity extends BriarActivity {
|
||||
importButton.setVisibility(GONE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
|
||||
String url = validateAndNormaliseUrl(urlInput.getText().toString());
|
||||
if (url == null) throw new AssertionError();
|
||||
importFeed(url);
|
||||
importFeed(urlInput.getText().toString());
|
||||
}
|
||||
|
||||
private void importFeed(final String url) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
@@ -12,8 +13,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
@UiThread
|
||||
|
||||
@@ -178,6 +178,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
notificationManager.blockAllContactNotifications();
|
||||
notificationManager.clearAllContactNotifications();
|
||||
eventBus.addListener(this);
|
||||
loadContacts();
|
||||
list.startPeriodicUpdate();
|
||||
@@ -187,6 +189,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
eventBus.removeListener(this);
|
||||
notificationManager.unblockAllContactNotifications();
|
||||
adapter.clear();
|
||||
list.showProgressBar();
|
||||
list.stopPeriodicUpdate();
|
||||
|
||||
@@ -17,7 +17,7 @@ public class DbControllerImpl implements DbController {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DbControllerImpl.class.getName());
|
||||
|
||||
protected final Executor dbExecutor;
|
||||
private final Executor dbExecutor;
|
||||
private final LifecycleManager lifecycleManager;
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.briarproject.briar.android.controller;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
@@ -13,7 +15,6 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@NotNullByDefault
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.forum;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
@@ -39,15 +38,16 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LEN
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class CreateForumActivity extends BriarActivity {
|
||||
public class CreateForumActivity extends BriarActivity
|
||||
implements OnEditorActionListener, OnClickListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CreateForumActivity.class.getName());
|
||||
|
||||
private TextInputLayout nameEntryLayout;
|
||||
private EditText nameEntry;
|
||||
private Button createForumButton;
|
||||
private ProgressBar progress;
|
||||
private TextView feedback;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
@@ -59,10 +59,12 @@ public class CreateForumActivity extends BriarActivity {
|
||||
|
||||
setContentView(R.layout.activity_create_forum);
|
||||
|
||||
nameEntryLayout =
|
||||
(TextInputLayout) findViewById(R.id.createForumNameLayout);
|
||||
nameEntry = (EditText) findViewById(R.id.createForumNameEntry);
|
||||
nameEntry.addTextChangedListener(new TextWatcher() {
|
||||
TextWatcher nameEntryWatcher = new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
@@ -70,39 +72,21 @@ public class CreateForumActivity extends BriarActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start,
|
||||
public void onTextChanged(CharSequence text, int start,
|
||||
int lengthBefore, int lengthAfter) {
|
||||
enableOrDisableCreateButton();
|
||||
}
|
||||
};
|
||||
nameEntry.setOnEditorActionListener(this);
|
||||
nameEntry.addTextChangedListener(nameEntryWatcher);
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
nameEntry.setOnEditorActionListener(new OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId,
|
||||
KeyEvent e) {
|
||||
createForum();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
feedback = (TextView) findViewById(R.id.createForumFeedback);
|
||||
|
||||
createForumButton = (Button) findViewById(R.id.createForumButton);
|
||||
createForumButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createForum();
|
||||
}
|
||||
});
|
||||
createForumButton.setOnClickListener(this);
|
||||
|
||||
progress = (ProgressBar) findViewById(R.id.createForumProgressBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
showSoftKeyboard(nameEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,27 +95,36 @@ public class CreateForumActivity extends BriarActivity {
|
||||
}
|
||||
|
||||
private void enableOrDisableCreateButton() {
|
||||
if (createForumButton == null) return; // Not created yet
|
||||
if (progress == null) return; // Not created yet
|
||||
createForumButton.setEnabled(validateName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
|
||||
hideSoftKeyboard(textView);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateName() {
|
||||
String name = nameEntry.getText().toString();
|
||||
int length = StringUtils.toUtf8(name).length;
|
||||
if (length > MAX_FORUM_NAME_LENGTH) {
|
||||
nameEntryLayout.setError(getString(R.string.name_too_long));
|
||||
feedback.setText(R.string.name_too_long);
|
||||
return false;
|
||||
}
|
||||
nameEntryLayout.setError(null);
|
||||
feedback.setText("");
|
||||
return length > 0;
|
||||
}
|
||||
|
||||
private void createForum() {
|
||||
if (!validateName()) return;
|
||||
hideSoftKeyboard(nameEntry);
|
||||
createForumButton.setVisibility(GONE);
|
||||
progress.setVisibility(VISIBLE);
|
||||
storeForum(nameEntry.getText().toString());
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view == createForumButton) {
|
||||
hideSoftKeyboard(view);
|
||||
if (!validateName()) return;
|
||||
createForumButton.setVisibility(GONE);
|
||||
progress.setVisibility(VISIBLE);
|
||||
storeForum(nameEntry.getText().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void storeForum(final String name) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.android.forum.ForumController.ForumListener;
|
||||
import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||
@@ -56,10 +55,10 @@ class ForumControllerImpl extends
|
||||
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
ForumManager forumManager, ForumSharingManager forumSharingManager,
|
||||
EventBus eventBus, Clock clock, MessageTracker messageTracker,
|
||||
EventBus eventBus, Clock clock,
|
||||
AndroidNotificationManager notificationManager) {
|
||||
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
|
||||
eventBus, clock, notificationManager, messageTracker);
|
||||
eventBus, clock, notificationManager);
|
||||
this.forumManager = forumManager;
|
||||
this.forumSharingManager = forumSharingManager;
|
||||
}
|
||||
@@ -85,7 +84,7 @@ class ForumControllerImpl extends
|
||||
(ForumInvitationResponseReceivedEvent) e;
|
||||
ForumInvitationResponse r =
|
||||
(ForumInvitationResponse) f.getResponse();
|
||||
if (r.getShareableId().equals(getGroupId()) && r.wasAccepted()) {
|
||||
if (r.getGroupId().equals(getGroupId()) && r.wasAccepted()) {
|
||||
LOG.info("Forum invitation was accepted");
|
||||
onForumInvitationAccepted(r.getContactId());
|
||||
}
|
||||
|
||||
@@ -119,6 +119,8 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
notificationManager.blockAllForumPostNotifications();
|
||||
notificationManager.clearAllForumPostNotifications();
|
||||
loadForums();
|
||||
loadAvailableForums();
|
||||
list.startPeriodicUpdate();
|
||||
@@ -127,6 +129,7 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
notificationManager.unblockAllForumPostNotifications();
|
||||
adapter.clear();
|
||||
list.showProgressBar();
|
||||
list.stopPeriodicUpdate();
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.briarproject.briar.android.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
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.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class SFDialogFragment extends DialogFragment {
|
||||
|
||||
public static SFDialogFragment newInstance(ArrayList<String> apps) {
|
||||
SFDialogFragment frag = new SFDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putStringArrayList("apps", apps);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(
|
||||
getActivity(),
|
||||
R.style.BriarDialogThemeNoFilter);
|
||||
builder.setTitle(R.string.screen_filter_title);
|
||||
LayoutInflater li = getActivity().getLayoutInflater();
|
||||
//Pass null here because it's an AlertDialog
|
||||
View v =
|
||||
li.inflate(R.layout.alert_dialog_checkbox, null,
|
||||
false);
|
||||
TextView t = (TextView) v.findViewById(R.id.alert_dialog_text);
|
||||
final ArrayList<String> apps =
|
||||
getArguments().getStringArrayList("apps");
|
||||
t.setText(getString(R.string.screen_filter_body, TextUtils
|
||||
.join("\n", apps)));
|
||||
final CheckBox cb =
|
||||
(CheckBox) v.findViewById(
|
||||
R.id.checkBox_screen_filter_reminder);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
((BaseActivity) getActivity())
|
||||
.rememberShownApps(apps, cb.isChecked());
|
||||
}
|
||||
});
|
||||
builder.setView(v);
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.briarproject.briar.android.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public class ScreenFilterDialogFragment extends DialogFragment {
|
||||
|
||||
public static ScreenFilterDialogFragment newInstance(
|
||||
ArrayList<String> apps) {
|
||||
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putStringArrayList("apps", apps);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
|
||||
R.style.BriarDialogThemeNoFilter);
|
||||
builder.setTitle(R.string.screen_filter_title);
|
||||
ArrayList<String> apps = getArguments().getStringArrayList("apps");
|
||||
builder.setMessage(getString(R.string.screen_filter_body,
|
||||
TextUtils.join("\n", apps)));
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -8,8 +9,6 @@ import android.view.ViewGroup;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class SignOutFragment extends BaseFragment {
|
||||
|
||||
private static final String TAG = SignOutFragment.class.getName();
|
||||
|
||||
@@ -33,6 +33,7 @@ import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
@@ -93,6 +94,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
View v = inflater.inflate(R.layout.introduction_message, container,
|
||||
false);
|
||||
ui = new ViewHolder(v);
|
||||
ui.text.setVisibility(GONE);
|
||||
ui.message.setSendButtonEnabled(false);
|
||||
|
||||
return v;
|
||||
@@ -154,11 +156,17 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
ui.contactName1.setText(c1.getAuthor().getName());
|
||||
ui.contactName2.setText(c2.getAuthor().getName());
|
||||
|
||||
// set introduction text
|
||||
ui.text.setText(String.format(
|
||||
getString(R.string.introduction_message_text),
|
||||
c1.getAuthor().getName(), c2.getAuthor().getName()));
|
||||
|
||||
// set button action
|
||||
ui.message.setListener(IntroductionMessageFragment.this);
|
||||
|
||||
// hide progress bar and show views
|
||||
ui.progressBar.setVisibility(GONE);
|
||||
ui.text.setVisibility(VISIBLE);
|
||||
ui.message.setSendButtonEnabled(true);
|
||||
ui.message.showSoftKeyboard();
|
||||
}
|
||||
@@ -226,6 +234,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
private final ProgressBar progressBar;
|
||||
private final CircleImageView avatar1, avatar2;
|
||||
private final TextView contactName1, contactName2;
|
||||
private final TextView text;
|
||||
private final TextInputView message;
|
||||
|
||||
private ViewHolder(View v) {
|
||||
@@ -234,6 +243,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
avatar2 = (CircleImageView) v.findViewById(R.id.avatarContact2);
|
||||
contactName1 = (TextView) v.findViewById(R.id.nameContact1);
|
||||
contactName2 = (TextView) v.findViewById(R.id.nameContact2);
|
||||
text = (TextView) v.findViewById(R.id.introductionText);
|
||||
message = (TextInputView) v
|
||||
.findViewById(R.id.introductionMessageView);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.blog.FeedFragment;
|
||||
import org.briarproject.briar.android.contact.ContactListFragment;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
@@ -46,9 +45,6 @@ import javax.inject.Inject;
|
||||
import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
||||
import static android.support.v4.view.GravityCompat.START;
|
||||
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
|
||||
|
||||
public class NavDrawerActivity extends BriarActivity implements
|
||||
BaseFragmentListener, TransportStateListener,
|
||||
@@ -132,12 +128,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
updateTransports();
|
||||
controller.showExpiryWarning(new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean showWarning) {
|
||||
if (showWarning) showExpiryWarning();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void exitIfStartupFailed(Intent intent) {
|
||||
@@ -264,34 +254,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
// Do nothing for now
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void showExpiryWarning() {
|
||||
int daysUntilExpiry = getDaysUntilExpiry();
|
||||
if (daysUntilExpiry < 0) signOut();
|
||||
|
||||
// show expiry warning text
|
||||
final ViewGroup
|
||||
expiryWarning = (ViewGroup) findViewById(R.id.expiryWarning);
|
||||
TextView expiryWarningText =
|
||||
(TextView) expiryWarning.findViewById(R.id.expiryWarningText);
|
||||
expiryWarningText.setText(getResources()
|
||||
.getQuantityString(R.plurals.expiry_warning, daysUntilExpiry,
|
||||
daysUntilExpiry));
|
||||
|
||||
// make close button functional
|
||||
ImageView expiryWarningClose =
|
||||
(ImageView) expiryWarning.findViewById(R.id.expiryWarningClose);
|
||||
expiryWarningClose.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
controller.expiryWarningDismissed();
|
||||
expiryWarning.setVisibility(GONE);
|
||||
}
|
||||
});
|
||||
|
||||
expiryWarning.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void initializeTransports(final LayoutInflater inflater) {
|
||||
transports = new ArrayList<>(3);
|
||||
|
||||
|
||||
@@ -3,15 +3,10 @@ package org.briarproject.briar.android.navdrawer;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.controller.handler.ResultHandler;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface NavDrawerController extends ActivityLifecycleController {
|
||||
|
||||
boolean isTransportRunning(TransportId transportId);
|
||||
|
||||
void showExpiryWarning(final ResultHandler<Boolean> handler);
|
||||
|
||||
void expiryWarningDismissed();
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.navdrawer;
|
||||
import android.app.Activity;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
@@ -15,10 +14,7 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.briar.android.controller.DbControllerImpl;
|
||||
import org.briarproject.briar.android.controller.handler.ResultHandler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
@@ -26,9 +22,6 @@ import java.util.logging.Logger;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -37,21 +30,18 @@ public class NavDrawerControllerImpl extends DbControllerImpl
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(NavDrawerControllerImpl.class.getName());
|
||||
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
private final SettingsManager settingsManager;
|
||||
private final EventBus eventBus;
|
||||
|
||||
private volatile TransportStateListener listener;
|
||||
|
||||
@Inject
|
||||
NavDrawerControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager, PluginManager pluginManager,
|
||||
SettingsManager settingsManager, EventBus eventBus) {
|
||||
LifecycleManager lifecycleManager,
|
||||
PluginManager pluginManager, EventBus eventBus) {
|
||||
super(dbExecutor, lifecycleManager);
|
||||
this.pluginManager = pluginManager;
|
||||
this.settingsManager = settingsManager;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@@ -102,63 +92,6 @@ public class NavDrawerControllerImpl extends DbControllerImpl
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showExpiryWarning(final ResultHandler<Boolean> handler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Settings settings =
|
||||
settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
|
||||
|
||||
if (warningInt == 0) {
|
||||
// we have not warned before
|
||||
handler.onResult(true);
|
||||
} else {
|
||||
long warningLong = warningInt * 1000L;
|
||||
long now = System.currentTimeMillis();
|
||||
long daysSinceLastWarning =
|
||||
(now - warningLong) / 1000 / 60 / 60 / 24;
|
||||
long daysBeforeExpiry =
|
||||
(EXPIRY_DATE - now) / 1000 / 60 / 60 / 24;
|
||||
|
||||
if (daysSinceLastWarning >= 30) {
|
||||
handler.onResult(true);
|
||||
return;
|
||||
}
|
||||
if (daysBeforeExpiry <= 3 && daysSinceLastWarning > 0) {
|
||||
handler.onResult(true);
|
||||
return;
|
||||
}
|
||||
handler.onResult(false);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expiryWarningDismissed() {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Settings settings = new Settings();
|
||||
int date = (int) (System.currentTimeMillis() / 1000L);
|
||||
settings.putInt(EXPIRY_DATE_WARNING, date);
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransportRunning(TransportId transportId) {
|
||||
Plugin plugin = pluginManager.getPlugin(transportId);
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener;
|
||||
import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.privategroup.GroupMember;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||
@@ -61,10 +60,9 @@ class GroupControllerImpl extends
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
PrivateGroupManager privateGroupManager,
|
||||
GroupMessageFactory groupMessageFactory, EventBus eventBus,
|
||||
MessageTracker messageTracker, Clock clock,
|
||||
AndroidNotificationManager notificationManager) {
|
||||
Clock clock, AndroidNotificationManager notificationManager) {
|
||||
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
|
||||
eventBus, clock, notificationManager, messageTracker);
|
||||
eventBus, clock, notificationManager);
|
||||
this.privateGroupManager = privateGroupManager;
|
||||
this.groupMessageFactory = groupMessageFactory;
|
||||
}
|
||||
@@ -108,7 +106,7 @@ class GroupControllerImpl extends
|
||||
(GroupInvitationResponseReceivedEvent) e;
|
||||
final GroupInvitationResponse r =
|
||||
(GroupInvitationResponse) g.getResponse();
|
||||
if (getGroupId().equals(r.getShareableId()) && r.wasAccepted()) {
|
||||
if (getGroupId().equals(r.getGroupId()) && r.wasAccepted()) {
|
||||
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
@@ -11,7 +12,6 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.threaded.ThreadItem;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@UiThread
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
||||
import org.briarproject.briar.android.threaded.BaseThreadItemViewHolder;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityIcon;
|
||||
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityString;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
@@ -17,10 +27,16 @@ class JoinMessageItemViewHolder
|
||||
extends BaseThreadItemViewHolder<GroupMessageItem> {
|
||||
|
||||
private final boolean isCreator;
|
||||
private final ImageView icon;
|
||||
private final TextView info;
|
||||
private final Button options;
|
||||
|
||||
JoinMessageItemViewHolder(View v, boolean isCreator) {
|
||||
super(v);
|
||||
this.isCreator = isCreator;
|
||||
icon = (ImageView) v.findViewById(R.id.icon);
|
||||
info = (TextView) v.findViewById(R.id.info);
|
||||
options = (Button) v.findViewById(R.id.optionsButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,6 +56,9 @@ class JoinMessageItemViewHolder
|
||||
getContext().getString(R.string.groups_member_joined,
|
||||
item.getAuthor().getName()));
|
||||
}
|
||||
icon.setVisibility(View.GONE);
|
||||
info.setVisibility(View.GONE);
|
||||
options.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void bind(final JoinMessageItem item) {
|
||||
@@ -56,6 +75,32 @@ class JoinMessageItemViewHolder
|
||||
item.getAuthor().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
if (item.getStatus() == OURSELVES || item.getStatus() == UNKNOWN) {
|
||||
icon.setVisibility(View.GONE);
|
||||
info.setVisibility(View.GONE);
|
||||
options.setVisibility(View.GONE);
|
||||
} else {
|
||||
icon.setVisibility(View.VISIBLE);
|
||||
icon.setImageResource(getVisibilityIcon(item.getVisibility()));
|
||||
info.setVisibility(View.VISIBLE);
|
||||
info.setText(getVisibilityString(getContext(), item.getVisibility(),
|
||||
item.getAuthor().getName()));
|
||||
if (item.getVisibility() == INVISIBLE) {
|
||||
options.setVisibility(View.VISIBLE);
|
||||
options.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i =
|
||||
new Intent(ctx, RevealContactsActivity.class);
|
||||
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
|
||||
ctx.startActivity(i);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
options.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.briarproject.briar.android.privategroup.creation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.android.contactselection.ContactSelectorActivity;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class BaseGroupInviteActivity
|
||||
extends ContactSelectorActivity implements MessageFragmentListener {
|
||||
|
||||
@Inject
|
||||
CreateGroupController controller;
|
||||
|
||||
@Override
|
||||
public void contactsSelected(Collection<ContactId> contacts) {
|
||||
super.contactsSelected(contacts);
|
||||
|
||||
showNextFragment(new CreateGroupMessageFragment());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onButtonClick(String message) {
|
||||
if (groupId == null)
|
||||
throw new IllegalStateException("GroupId was not initialized");
|
||||
controller.sendInvitation(groupId, contacts, message,
|
||||
new UiResultExceptionHandler<Void, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(Void result) {
|
||||
setResult(RESULT_OK);
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
setResult(RESULT_CANCELED);
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumMessageLength() {
|
||||
return MAX_GROUP_INVITATION_MSG_LENGTH;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.privategroup.creation;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -9,20 +10,14 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class CreateGroupActivity extends BriarActivity
|
||||
implements CreateGroupListener {
|
||||
|
||||
@Inject
|
||||
CreateGroupController controller;
|
||||
public class CreateGroupActivity extends BaseGroupInviteActivity implements
|
||||
CreateGroupListener, MessageFragmentListener {
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
@@ -33,20 +28,32 @@ public class CreateGroupActivity extends BriarActivity
|
||||
public void onCreate(@Nullable Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
|
||||
if (bundle == null) {
|
||||
showInitialFragment(new CreateGroupFragment());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
||||
// At this point, the group had been created already,
|
||||
// so don't allow to create it again.
|
||||
openNewGroup();
|
||||
overridePendingTransition(R.anim.screen_old_in,
|
||||
R.anim.screen_new_out);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupNameChosen(String name) {
|
||||
controller.createGroup(name,
|
||||
new UiResultExceptionHandler<GroupId, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(GroupId g) {
|
||||
openNewGroup(g);
|
||||
groupId = g;
|
||||
switchToContactSelectorFragment(g);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,10 +63,16 @@ public class CreateGroupActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void openNewGroup(GroupId g) {
|
||||
private void switchToContactSelectorFragment(GroupId g) {
|
||||
showNextFragment(GroupInviteFragment.newInstance(g));
|
||||
}
|
||||
|
||||
private void openNewGroup() {
|
||||
Intent i = new Intent(this, GroupActivity.class);
|
||||
i.putExtra(GROUP_ID, g.getBytes());
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivity(i);
|
||||
// finish this activity, so we can't come back to it
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,27 +2,18 @@ package org.briarproject.briar.android.privategroup.creation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
||||
|
||||
public class CreateGroupFragment extends BaseFragment {
|
||||
@@ -30,10 +21,8 @@ public class CreateGroupFragment extends BaseFragment {
|
||||
public final static String TAG = CreateGroupFragment.class.getName();
|
||||
|
||||
private CreateGroupListener listener;
|
||||
private EditText nameEntry;
|
||||
private Button createGroupButton;
|
||||
private TextInputLayout nameLayout;
|
||||
private ProgressBar progress;
|
||||
private EditText name;
|
||||
private Button button;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
@@ -45,54 +34,42 @@ public class CreateGroupFragment extends BaseFragment {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
// inflate view
|
||||
View v = inflater.inflate(R.layout.fragment_create_group, container,
|
||||
false);
|
||||
nameEntry = (EditText) v.findViewById(R.id.name);
|
||||
nameEntry.addTextChangedListener(new TextWatcher() {
|
||||
|
||||
name = (EditText) v.findViewById(R.id.name);
|
||||
name.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start,
|
||||
int lengthBefore, int lengthAfter) {
|
||||
enableOrDisableCreateButton();
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
validateName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
nameEntry.setOnEditorActionListener(new OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId,
|
||||
KeyEvent e) {
|
||||
createGroup();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
nameLayout = (TextInputLayout) v.findViewById(R.id.nameLayout);
|
||||
|
||||
createGroupButton = (Button) v.findViewById(R.id.button);
|
||||
createGroupButton.setOnClickListener(new OnClickListener() {
|
||||
button = (Button) v.findViewById(R.id.button);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createGroup();
|
||||
listener.hideSoftKeyboard(name);
|
||||
listener.onGroupNameChosen(name.getText().toString());
|
||||
}
|
||||
});
|
||||
|
||||
progress = (ProgressBar) v.findViewById(R.id.progressBar);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
listener.showSoftKeyboard(nameEntry);
|
||||
listener.showSoftKeyboard(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,27 +82,12 @@ public class CreateGroupFragment extends BaseFragment {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void enableOrDisableCreateButton() {
|
||||
if (createGroupButton == null) return; // Not created yet
|
||||
createGroupButton.setEnabled(validateName());
|
||||
private void validateName() {
|
||||
String name = this.name.getText().toString();
|
||||
if (name.length() < 1 || name.length() > MAX_GROUP_NAME_LENGTH)
|
||||
button.setEnabled(false);
|
||||
else if(!button.isEnabled())
|
||||
button.setEnabled(true);
|
||||
}
|
||||
|
||||
private boolean validateName() {
|
||||
String name = nameEntry.getText().toString();
|
||||
int length = StringUtils.toUtf8(name).length;
|
||||
if (length > MAX_GROUP_NAME_LENGTH) {
|
||||
nameLayout.setError(getString(R.string.name_too_long));
|
||||
return false;
|
||||
}
|
||||
nameLayout.setError(null);
|
||||
return length > 0;
|
||||
}
|
||||
|
||||
private void createGroup() {
|
||||
if (!validateName()) return;
|
||||
listener.hideSoftKeyboard(nameEntry);
|
||||
createGroupButton.setVisibility(GONE);
|
||||
progress.setVisibility(VISIBLE);
|
||||
listener.onGroupNameChosen(nameEntry.getText().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class CreateGroupModule {
|
||||
public class GroupCreateModule {
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
@@ -3,43 +3,25 @@ package org.briarproject.briar.android.privategroup.creation;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.contactselection.ContactSelectorActivity;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class GroupInviteActivity extends ContactSelectorActivity
|
||||
public class GroupInviteActivity extends BaseGroupInviteActivity
|
||||
implements MessageFragmentListener {
|
||||
|
||||
@Inject
|
||||
CreateGroupController controller;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle bundle) {
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] g = i.getByteArrayExtra(GROUP_ID);
|
||||
if (g == null) throw new IllegalStateException("No GroupId in intent");
|
||||
if (g == null) throw new IllegalStateException("No GroupId in intent.");
|
||||
groupId = new GroupId(g);
|
||||
|
||||
if (bundle == null) {
|
||||
@@ -47,36 +29,4 @@ public class GroupInviteActivity extends ContactSelectorActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contactsSelected(Collection<ContactId> contacts) {
|
||||
super.contactsSelected(contacts);
|
||||
|
||||
showNextFragment(new CreateGroupMessageFragment());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onButtonClick(String message) {
|
||||
if (groupId == null)
|
||||
throw new IllegalStateException("GroupId was not initialized");
|
||||
controller.sendInvitation(groupId, contacts, message,
|
||||
new UiResultExceptionHandler<Void, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(Void result) {
|
||||
setResult(RESULT_OK);
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
setResult(RESULT_CANCELED);
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumMessageLength() {
|
||||
return MAX_GROUP_INVITATION_MSG_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,12 +79,15 @@ class GroupListControllerImpl extends DbControllerImpl
|
||||
throw new IllegalStateException(
|
||||
"GroupListListener needs to be attached");
|
||||
eventBus.addListener(this);
|
||||
notificationManager.blockAllGroupMessageNotifications();
|
||||
notificationManager.clearAllGroupMessageNotifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStop() {
|
||||
eventBus.removeListener(this);
|
||||
notificationManager.unblockAllGroupMessageNotifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,6 +36,7 @@ class MemberListAdapter extends
|
||||
@Override
|
||||
public boolean areContentsTheSame(MemberListItem m1, MemberListItem m2) {
|
||||
if (m1.isOnline() != m2.isOnline()) return false;
|
||||
if (m1.getVisibility() != m2.getVisibility()) return false;
|
||||
if (m1.getContactId() != m2.getContactId()) return false;
|
||||
if (m1.getStatus() != m2.getStatus()) return false;
|
||||
return true;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package org.briarproject.briar.android.privategroup.memberlist;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.Author.Status;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.privategroup.GroupMember;
|
||||
import org.briarproject.briar.api.privategroup.Visibility;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
@@ -38,6 +40,10 @@ class MemberListItem {
|
||||
return groupMember.getContactId();
|
||||
}
|
||||
|
||||
Visibility getVisibility() {
|
||||
return groupMember.getVisibility();
|
||||
}
|
||||
|
||||
boolean isOnline() {
|
||||
return online;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.AuthorView;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
|
||||
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityIcon;
|
||||
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityString;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
@@ -19,12 +22,16 @@ class MemberListItemHolder extends RecyclerView.ViewHolder {
|
||||
private final AuthorView author;
|
||||
private final ImageView bulb;
|
||||
private final TextView creator;
|
||||
private final ImageView icon;
|
||||
private final TextView info;
|
||||
|
||||
MemberListItemHolder(View v) {
|
||||
super(v);
|
||||
author = (AuthorView) v.findViewById(R.id.authorView);
|
||||
bulb = (ImageView) v.findViewById(R.id.bulbView);
|
||||
creator = (TextView) v.findViewById(R.id.creatorView);
|
||||
icon = (ImageView) v.findViewById(R.id.icon);
|
||||
info = (TextView) v.findViewById(R.id.info);
|
||||
}
|
||||
|
||||
protected void bind(MemberListItem item) {
|
||||
@@ -57,6 +64,19 @@ class MemberListItemHolder extends RecyclerView.ViewHolder {
|
||||
} else {
|
||||
creator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// visibility information
|
||||
if (item.getStatus() == OURSELVES || item.getStatus() == UNKNOWN) {
|
||||
icon.setVisibility(View.GONE);
|
||||
info.setVisibility(View.GONE);
|
||||
} else {
|
||||
icon.setVisibility(View.VISIBLE);
|
||||
icon.setImageResource(getVisibilityIcon(item.getVisibility()));
|
||||
info.setVisibility(View.VISIBLE);
|
||||
info.setText(
|
||||
getVisibilityString(info.getContext(), item.getVisibility(),
|
||||
item.getMember().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ public class BriarReportPrimer implements ReportPrimer {
|
||||
NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
|
||||
boolean wifiAvailable = wifi != null && wifi.isAvailable();
|
||||
// Is wifi enabled?
|
||||
o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
|
||||
o = ctx.getSystemService(WIFI_SERVICE);
|
||||
WifiManager wm = (WifiManager) o;
|
||||
boolean wifiEnabled = wm != null &&
|
||||
wm.getWifiState() == WIFI_STATE_ENABLED;
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Intent;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
@@ -55,7 +54,6 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGT
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_LOCK_SCREEN;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_PRIVATE;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_NAME;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_URI;
|
||||
@@ -83,8 +81,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
private CheckBoxPreference notifyForumPosts;
|
||||
private CheckBoxPreference notifyBlogPosts;
|
||||
private CheckBoxPreference notifyVibration;
|
||||
private CheckBoxPreference notifyLockscreen;
|
||||
|
||||
private Preference notifySound;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@@ -118,8 +114,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
"pref_key_notify_blog_posts");
|
||||
notifyVibration = (CheckBoxPreference) findPreference(
|
||||
"pref_key_notify_vibration");
|
||||
notifyLockscreen = (CheckBoxPreference) findPreference(
|
||||
"pref_key_notify_lock_screen");
|
||||
notifySound = findPreference("pref_key_notify_sound");
|
||||
|
||||
enableBluetooth.setOnPreferenceChangeListener(this);
|
||||
@@ -129,10 +123,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
notifyForumPosts.setOnPreferenceChangeListener(this);
|
||||
notifyBlogPosts.setOnPreferenceChangeListener(this);
|
||||
notifyVibration.setOnPreferenceChangeListener(this);
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
notifyLockscreen.setVisible(true);
|
||||
notifyLockscreen.setOnPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
notifySound.setOnPreferenceClickListener(
|
||||
new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
@@ -243,9 +234,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
notifyVibration.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_VIBRATION, true));
|
||||
|
||||
notifyLockscreen.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_LOCK_SCREEN, false));
|
||||
|
||||
String text;
|
||||
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
||||
String ringtoneName =
|
||||
@@ -302,10 +290,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) o);
|
||||
storeSettings(s);
|
||||
} else if (preference == notifyLockscreen) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_NOTIFY_LOCK_SCREEN, (Boolean) o);
|
||||
storeSettings(s);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.sharing;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.MenuItem;
|
||||
@@ -24,7 +25,6 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@@ -4,6 +4,9 @@ import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.StrictMode;
|
||||
import android.os.StrictMode.ThreadPolicy;
|
||||
import android.os.StrictMode.VmPolicy;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
import android.transition.Fade;
|
||||
|
||||
@@ -19,18 +22,27 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
|
||||
import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
|
||||
import static org.briarproject.briar.android.TestingConstants.TESTING;
|
||||
|
||||
public class SplashScreenActivity extends BaseActivity {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SplashScreenActivity.class.getName());
|
||||
|
||||
// This build expires on 1 May 2017
|
||||
private static final long EXPIRY_DATE = 1493593200 * 1000L;
|
||||
|
||||
@Inject
|
||||
protected ConfigController configController;
|
||||
@Inject
|
||||
protected AndroidExecutor androidExecutor;
|
||||
|
||||
public SplashScreenActivity() {
|
||||
Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
|
||||
enableStrictMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
@@ -71,6 +83,23 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNewScreenFilterWarning() {
|
||||
}
|
||||
|
||||
private void enableStrictMode() {
|
||||
if (TESTING) {
|
||||
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
|
||||
threadPolicy.detectAll();
|
||||
threadPolicy.penaltyLog();
|
||||
StrictMode.setThreadPolicy(threadPolicy.build());
|
||||
VmPolicy.Builder vmPolicy = new VmPolicy.Builder();
|
||||
vmPolicy.detectAll();
|
||||
vmPolicy.penaltyLog();
|
||||
StrictMode.setVmPolicy(vmPolicy.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void setPreferencesDefaults() {
|
||||
androidExecutor.runOnBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -14,8 +14,6 @@ import org.briarproject.briar.android.util.VersionedAdapter;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||
|
||||
@UiThread
|
||||
@@ -28,7 +26,6 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
protected final NestedTreeList<I> items = new NestedTreeList<>();
|
||||
private final ThreadItemListener<I> listener;
|
||||
private final LinearLayoutManager layoutManager;
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private volatile int revision = 0;
|
||||
|
||||
@@ -67,17 +64,6 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
revision++;
|
||||
}
|
||||
|
||||
void setItemWithIdVisible(MessageId messageId) {
|
||||
int pos = 0;
|
||||
for (I item : items) {
|
||||
if (item.getId().equals(messageId)) {
|
||||
layoutManager.scrollToPosition(pos);
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
public void setItems(Collection<I> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
@@ -158,7 +144,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
/**
|
||||
* Returns the position of the first unread item below the current viewport
|
||||
*/
|
||||
int getVisibleUnreadPosBottom() {
|
||||
public int getVisibleUnreadPosBottom() {
|
||||
final int positionBottom = layoutManager.findLastVisibleItemPosition();
|
||||
if (positionBottom == NO_POSITION) return NO_POSITION;
|
||||
for (int i = positionBottom + 1; i < items.size(); i++) {
|
||||
@@ -170,7 +156,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
/**
|
||||
* Returns the position of the first unread item above the current viewport
|
||||
*/
|
||||
int getVisibleUnreadPosTop() {
|
||||
public int getVisibleUnreadPosTop() {
|
||||
final int positionTop = layoutManager.findFirstVisibleItemPosition();
|
||||
int position = NO_POSITION;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface ThreadItemList<I extends ThreadItem> extends List<I> {
|
||||
|
||||
@Nullable
|
||||
MessageId getFirstVisibleItemId();
|
||||
|
||||
void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ThreadItemListImpl<I extends ThreadItem> extends ArrayList<I>
|
||||
implements ThreadItemList<I> {
|
||||
|
||||
private MessageId bottomVisibleItemId;
|
||||
|
||||
@Override
|
||||
public MessageId getFirstVisibleItemId() {
|
||||
return bottomVisibleItemId;
|
||||
}
|
||||
|
||||
public void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId) {
|
||||
this.bottomVisibleItemId = bottomVisibleItemId;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.threaded;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.Snackbar;
|
||||
@@ -25,7 +26,6 @@ import org.briarproject.briar.android.controller.SharingController;
|
||||
import org.briarproject.briar.android.controller.SharingController.SharingListener;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDataSource;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
@@ -38,7 +38,6 @@ import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.design.widget.Snackbar.make;
|
||||
@@ -52,7 +51,7 @@ import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCo
|
||||
public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader>
|
||||
extends BriarActivity
|
||||
implements ThreadListListener<H>, TextInputListener, SharingListener,
|
||||
ThreadItemListener<I>, ThreadListDataSource {
|
||||
ThreadItemListener<I> {
|
||||
|
||||
protected static final String KEY_REPLY_ID = "replyId";
|
||||
|
||||
@@ -69,7 +68,6 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
private MessageId replyId;
|
||||
|
||||
protected abstract ThreadListController<G, I, H> getController();
|
||||
|
||||
@Inject
|
||||
protected SharingController sharingController;
|
||||
|
||||
@@ -106,7 +104,6 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
updateUnreadCount();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView,
|
||||
int newState) {
|
||||
@@ -142,22 +139,11 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
if (replyIdBytes != null) replyId = new MessageId(replyIdBytes);
|
||||
}
|
||||
|
||||
loadItems();
|
||||
sharingController.setSharingListener(this);
|
||||
loadSharingContacts();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MessageId getFirstVisibleMessageId() {
|
||||
if (layoutManager != null && adapter != null) {
|
||||
int position =
|
||||
layoutManager.findFirstVisibleItemPosition();
|
||||
I i = adapter.getItemAt(position);
|
||||
return i == null ? null : i.getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract A createAdapter(LinearLayoutManager layoutManager);
|
||||
|
||||
protected void loadNamedGroup() {
|
||||
@@ -181,16 +167,16 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
protected void loadItems() {
|
||||
final int revision = adapter.getRevision();
|
||||
getController().loadItems(
|
||||
new UiResultExceptionHandler<ThreadItemList<I>, DbException>(
|
||||
this) {
|
||||
new UiResultExceptionHandler<Collection<I>, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(ThreadItemList<I> items) {
|
||||
public void onResultUi(Collection<I> items) {
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
if (items.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
initList(items);
|
||||
adapter.setItems(items);
|
||||
list.showData();
|
||||
updateTextInput(replyId);
|
||||
}
|
||||
} else {
|
||||
@@ -206,15 +192,6 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
});
|
||||
}
|
||||
|
||||
private void initList(final ThreadItemList<I> items) {
|
||||
adapter.setItems(items);
|
||||
MessageId messageId = items.getFirstVisibleItemId();
|
||||
if (messageId != null)
|
||||
adapter.setItemWithIdVisible(messageId);
|
||||
updateUnreadCount();
|
||||
list.showData();
|
||||
}
|
||||
|
||||
protected void loadSharingContacts() {
|
||||
getController().loadSharingContacts(
|
||||
new UiResultExceptionHandler<Collection<ContactId>, DbException>(
|
||||
@@ -238,7 +215,6 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
sharingController.onStart();
|
||||
loadItems();
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
@@ -31,7 +30,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
||||
|
||||
void loadItem(H header, ResultExceptionHandler<I, DbException> handler);
|
||||
|
||||
void loadItems(ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
|
||||
void loadItems(ResultExceptionHandler<Collection<I>, DbException> handler);
|
||||
|
||||
void markItemRead(I item);
|
||||
|
||||
@@ -42,7 +41,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
||||
|
||||
void deleteNamedGroup(ExceptionHandler<DbException> handler);
|
||||
|
||||
interface ThreadListListener<H> extends ThreadListDataSource {
|
||||
interface ThreadListListener<H> extends DestroyableContext {
|
||||
@UiThread
|
||||
void onHeaderReceived(H header);
|
||||
|
||||
@@ -53,10 +52,4 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
|
||||
void onInvitationAccepted(ContactId c);
|
||||
}
|
||||
|
||||
interface ThreadListDataSource extends DestroyableContext {
|
||||
|
||||
@UiThread @Nullable
|
||||
MessageId getFirstVisibleMessageId();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,13 +22,14 @@ import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
import org.briarproject.briar.api.client.PostHeader;
|
||||
import org.briarproject.briar.api.client.ThreadedMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -54,21 +55,18 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
protected final AndroidNotificationManager notificationManager;
|
||||
protected final Executor cryptoExecutor;
|
||||
protected final Clock clock;
|
||||
private final MessageTracker messageTracker;
|
||||
protected volatile L listener;
|
||||
|
||||
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager, IdentityManager identityManager,
|
||||
@CryptoExecutor Executor cryptoExecutor, EventBus eventBus,
|
||||
Clock clock, AndroidNotificationManager notificationManager,
|
||||
MessageTracker messageTracker) {
|
||||
Clock clock, AndroidNotificationManager notificationManager) {
|
||||
super(dbExecutor, lifecycleManager);
|
||||
this.identityManager = identityManager;
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.notificationManager = notificationManager;
|
||||
this.clock = clock;
|
||||
this.eventBus = eventBus;
|
||||
this.messageTracker = messageTracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,21 +97,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
final MessageId messageId = listener.getFirstVisibleMessageId();
|
||||
if (messageId != null) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
messageTracker
|
||||
.storeMessageId(groupId, messageId);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@@ -161,7 +144,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
|
||||
@Override
|
||||
public void loadItems(
|
||||
final ResultExceptionHandler<ThreadItemList<I>, DbException> handler) {
|
||||
final ResultExceptionHandler<Collection<I>, DbException> handler) {
|
||||
checkGroupId();
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
@@ -310,16 +293,11 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
|
||||
@DatabaseExecutor
|
||||
protected abstract void deleteNamedGroup(G groupItem) throws DbException;
|
||||
|
||||
private ThreadItemList<I> buildItems(Collection<H> headers)
|
||||
throws DbException {
|
||||
ThreadItemList<I> items = new ThreadItemListImpl<>();
|
||||
private List<I> buildItems(Collection<H> headers) {
|
||||
List<I> items = new ArrayList<>();
|
||||
for (H h : headers) {
|
||||
items.add(buildItem(h, bodyCache.get(h.getId())));
|
||||
}
|
||||
MessageId msgId = messageTracker.loadStoredMessageId(groupId);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loaded last top visible message id " + msgId);
|
||||
items.setFirstVisibleId(msgId);
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
@@ -22,8 +23,6 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.ArticleMovementMethod;
|
||||
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.text.format.DateUtils.DAY_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
|
||||
import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE;
|
||||
@@ -31,7 +30,6 @@ import static android.text.format.DateUtils.FORMAT_ABBREV_TIME;
|
||||
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
|
||||
|
||||
public class UiUtils {
|
||||
|
||||
@@ -65,12 +63,6 @@ public class UiUtils {
|
||||
MIN_DATE_RESOLUTION, flags).toString();
|
||||
}
|
||||
|
||||
public static int getDaysUntilExpiry() {
|
||||
long now = System.currentTimeMillis();
|
||||
long daysBeforeExpiry = (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24;
|
||||
return (int) daysBeforeExpiry;
|
||||
}
|
||||
|
||||
public static SpannableStringBuilder getTeaser(Context ctx, Spanned body) {
|
||||
if (body.length() < TEASER_LENGTH)
|
||||
throw new IllegalArgumentException(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.DimenRes;
|
||||
@@ -15,7 +16,9 @@ import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.Author.Status;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.blog.BlogActivity;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -24,10 +27,12 @@ import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.graphics.Typeface.BOLD;
|
||||
import static android.util.TypedValue.COMPLEX_UNIT_PX;
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.NONE;
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
|
||||
@UiThread
|
||||
public class AuthorView extends RelativeLayout {
|
||||
@@ -105,16 +110,24 @@ public class AuthorView extends RelativeLayout {
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setAuthorClickable(OnClickListener listener) {
|
||||
public void setBlogLink(final GroupId groupId) {
|
||||
setClickable(true);
|
||||
TypedValue outValue = new TypedValue();
|
||||
getContext().getTheme().resolveAttribute(
|
||||
android.R.attr.selectableItemBackground, outValue, true);
|
||||
setBackgroundResource(outValue.resourceId);
|
||||
setOnClickListener(listener);
|
||||
setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
getContext().startActivity(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setAuthorNotClickable() {
|
||||
public void unsetBlogLink() {
|
||||
setClickable(false);
|
||||
setBackgroundResource(android.R.color.transparent);
|
||||
setOnClickListener(null);
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.util.AttributeSet;
|
||||
@@ -12,8 +13,6 @@ import android.widget.TextView;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public class UnreadMessageButton extends FrameLayout {
|
||||
@@ -37,7 +36,8 @@ public class UnreadMessageButton extends FrameLayout {
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.unread_message_button, this, true);
|
||||
inflater
|
||||
.inflate(R.layout.unread_message_button, this, true);
|
||||
|
||||
fab = (FloatingActionButton) findViewById(R.id.fab);
|
||||
unread = (TextView) findViewById(R.id.unreadCountView);
|
||||
@@ -64,11 +64,15 @@ public class UnreadMessageButton extends FrameLayout {
|
||||
|
||||
public void setUnreadCount(int count) {
|
||||
if (count == 0) {
|
||||
setVisibility(INVISIBLE);
|
||||
fab.setVisibility(GONE);
|
||||
// fab.hide();
|
||||
unread.setVisibility(GONE);
|
||||
} else {
|
||||
// FIXME: Use animations when upgrading to support library 24.2.0
|
||||
// https://code.google.com/p/android/issues/detail?id=216469
|
||||
setVisibility(VISIBLE);
|
||||
fab.setVisibility(VISIBLE);
|
||||
// if (!fab.isShown()) fab.show();
|
||||
unread.setVisibility(VISIBLE);
|
||||
unread.setText(String.valueOf(count));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.briarproject.briar.android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.view.MotionEvent.FLAG_WINDOW_IS_OBSCURED;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TapSafeFrameLayout extends FrameLayout {
|
||||
|
||||
@Nullable
|
||||
private OnTapFilteredListener listener;
|
||||
|
||||
public TapSafeFrameLayout(Context context) {
|
||||
super(context);
|
||||
setFilterTouchesWhenObscured(false);
|
||||
}
|
||||
|
||||
public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setFilterTouchesWhenObscured(false);
|
||||
}
|
||||
|
||||
public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs,
|
||||
@AttrRes int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setFilterTouchesWhenObscured(false);
|
||||
}
|
||||
|
||||
public void setOnTapFilteredListener(OnTapFilteredListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFilterTouchEventForSecurity(MotionEvent e) {
|
||||
boolean filter = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0;
|
||||
if (filter && listener != null) listener.onTapFiltered();
|
||||
return !filter;
|
||||
}
|
||||
|
||||
public interface OnTapFilteredListener {
|
||||
void onTapFiltered();
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,19 @@ public interface AndroidNotificationManager {
|
||||
String PREF_NOTIFY_RINGTONE_NAME = "notifyRingtoneName";
|
||||
String PREF_NOTIFY_RINGTONE_URI = "notifyRingtoneUri";
|
||||
String PREF_NOTIFY_VIBRATION = "notifyVibration";
|
||||
String PREF_NOTIFY_LOCK_SCREEN = "notifyLockScreen";
|
||||
|
||||
void clearContactNotification(ContactId c);
|
||||
|
||||
void clearAllContactNotifications();
|
||||
|
||||
void clearGroupMessageNotification(GroupId g);
|
||||
|
||||
void clearAllGroupMessageNotifications();
|
||||
|
||||
void clearForumPostNotification(GroupId g);
|
||||
|
||||
void clearAllForumPostNotifications();
|
||||
|
||||
void clearBlogPostNotification(GroupId g);
|
||||
|
||||
void clearAllBlogPostNotifications();
|
||||
@@ -38,6 +43,18 @@ public interface AndroidNotificationManager {
|
||||
|
||||
void unblockNotification(GroupId g);
|
||||
|
||||
void blockAllContactNotifications();
|
||||
|
||||
void unblockAllContactNotifications();
|
||||
|
||||
void blockAllGroupMessageNotifications();
|
||||
|
||||
void unblockAllGroupMessageNotifications();
|
||||
|
||||
void blockAllForumPostNotifications();
|
||||
|
||||
void unblockAllForumPostNotifications();
|
||||
|
||||
void blockAllBlogPostNotifications();
|
||||
|
||||
void unblockAllBlogPostNotifications();
|
||||
|
||||
@@ -2,10 +2,14 @@ package org.briarproject.briar.api.android;
|
||||
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
public interface ScreenFilterMonitor {
|
||||
|
||||
@UiThread
|
||||
Set<String> getApps();
|
||||
|
||||
@UiThread
|
||||
void storeAppsAsShown(Collection<String> s, boolean persistent);
|
||||
}
|
||||
|
||||
@@ -104,9 +104,8 @@ public class EmojiPageView extends FrameLayout {
|
||||
emojiSize + 2 * pad));
|
||||
view = emojiView;
|
||||
}
|
||||
String emoji = model.getEmoji()[position];
|
||||
view.setEmoji(emoji);
|
||||
|
||||
view.setEmoji(model.getEmoji()[position]);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint.FontMetricsInt;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.AppCompatTextView;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.text.TextUtils.TruncateAt.END;
|
||||
import static android.view.View.MeasureSpec.AT_MOST;
|
||||
import static android.view.View.MeasureSpec.EXACTLY;
|
||||
import static android.widget.TextView.BufferType.SPANNABLE;
|
||||
|
||||
@UiThread
|
||||
public class EmojiTextView extends AppCompatTextView {
|
||||
public class EmojiTextView extends TextView {
|
||||
|
||||
private CharSequence source;
|
||||
private boolean needsEllipsizing;
|
||||
|
||||
public EmojiTextView(Context context) {
|
||||
this(context, null);
|
||||
@@ -34,9 +42,13 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
|
||||
@Override
|
||||
public void setText(@Nullable CharSequence text, BufferType type) {
|
||||
CharSequence source =
|
||||
EmojiProvider.getInstance(getContext()).emojify(text, this);
|
||||
super.setText(source, SPANNABLE);
|
||||
source = EmojiProvider.getInstance(getContext()).emojify(text, this);
|
||||
|
||||
setTextEllipsized(source);
|
||||
}
|
||||
|
||||
private void setTextEllipsized(final @Nullable CharSequence source) {
|
||||
super.setText(needsEllipsizing ? ellipsize(source) : source, SPANNABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,6 +57,26 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
else super.invalidateDrawable(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int size = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int mode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
if (getEllipsize() == END &&
|
||||
!TextUtils.isEmpty(source) &&
|
||||
(mode == AT_MOST || mode == EXACTLY) &&
|
||||
getPaint().breakText(source, 0, source.length() - 1, true, size,
|
||||
null) != source.length()) {
|
||||
needsEllipsizing = true;
|
||||
FontMetricsInt font = getPaint().getFontMetricsInt();
|
||||
int height = Math.abs(font.top - font.bottom);
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(size, EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, EXACTLY));
|
||||
} else {
|
||||
needsEllipsizing = false;
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right,
|
||||
int bottom) {
|
||||
@@ -57,6 +89,20 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
if (size > drawingCacheSize) {
|
||||
setLayerType(LAYER_TYPE_NONE, null);
|
||||
}
|
||||
|
||||
if (changed) setTextEllipsized(source);
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CharSequence ellipsize(@Nullable CharSequence text) {
|
||||
if (TextUtils.isEmpty(text) || getWidth() == 0 ||
|
||||
getEllipsize() != END) {
|
||||
return text;
|
||||
} else {
|
||||
return TextUtils.ellipsize(text, getPaint(),
|
||||
getWidth() - getPaddingRight() - getPaddingLeft(), END);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(RecentEmojiPageModel.class.getName());
|
||||
|
||||
private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent2";
|
||||
private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent";
|
||||
private static final int EMOJI_LRU_SIZE = 50;
|
||||
|
||||
private final LinkedHashSet<String> recentlyUsed; // UI thread
|
||||
@@ -98,12 +98,12 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
}
|
||||
|
||||
private String serialize(LinkedHashSet<String> emojis) {
|
||||
return StringUtils.join(emojis, "\t");
|
||||
return StringUtils.join(emojis, ";");
|
||||
}
|
||||
|
||||
private LinkedHashSet<String> deserialize(@Nullable String serialized) {
|
||||
if (serialized == null) return new LinkedHashSet<>();
|
||||
String[] list = serialized.split("\t");
|
||||
String[] list = serialized.split(";");
|
||||
LinkedHashSet<String> result = new LinkedHashSet<>(list.length);
|
||||
Collections.addAll(result, list);
|
||||
return result;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="18dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<path
|
||||
android:fillColor="#ffa500"
|
||||
android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z"/>
|
||||
android:pathData="M0,8.88178e-16 L30,8.88178e-16 L30,30 L0,30 L0,8.88178e-16 Z"/>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M8.9322,18.0339 C10.6078,18.0339,11.9661,19.3922,11.9661,21.0678
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
android:fillColor="#87c214"
|
||||
android:pathData="M64.9004,0 C55.2004,0,47.1992,7.99922,47.1992,17.6992 L47.1992,40.1992
|
||||
L90.8008,40.1992 L90.8008,17.6992
|
||||
C90.8008,7.99922,82.8992,0,73.1992,0 L64.9004,0 Z M161.9,0
|
||||
C90.8008,7.99922,82.8992,-4.73695e-15,73.1992,0 L64.9004,0 Z M161.9,0
|
||||
C152.2,0,144.199,7.99922,144.199,17.6992 L144.199,137.199 L187.801,137.199
|
||||
L187.801,17.6992 C187.801,7.99922,179.899,0,170.199,0 L161.9,0 Z
|
||||
L187.801,17.6992 C187.801,7.99922,179.899,-4.73695e-15,170.199,0 L161.9,0 Z
|
||||
M47.1992,97.8008 L47.1992,217.301 C47.1992,227.001,55.1004,235,64.9004,235
|
||||
L73.1992,235 C82.8992,235,90.9004,227.001,90.9004,217.301 L90.9004,97.8008
|
||||
L47.1992,97.8008 Z M144.199,194.801 L144.199,217.301
|
||||
@@ -25,12 +25,12 @@ C179.899,235,187.9,227.001,187.9,217.301 L187.9,194.801 L144.199,194.801 Z"/>
|
||||
android:pathData="M144.2,144.2 L187.9,144.2 L187.9,187.9 L144.2,187.9 L144.2,144.2 Z"/>
|
||||
<path
|
||||
android:fillColor="#95d220"
|
||||
android:pathData="M17.6992,47.1992 C7.99922,47.1992,0,55.1004,0,64.9004 L0,73.1992
|
||||
android:pathData="M17.6992,47.1992 C7.99922,47.1992,2.36848e-15,55.1004,0,64.9004 L0,73.1992
|
||||
C0,82.8992,7.89922,90.9004,17.6992,90.9004 L137.199,90.9004 L137.199,47.1992
|
||||
L17.6992,47.1992 Z M194.801,47.1992 L194.801,90.9004 L217.301,90.9004
|
||||
C227.001,90.9004,235,82.9992,235,73.1992 L235,64.9004
|
||||
C235,55.1004,227.001,47.1992,217.301,47.1992 L194.801,47.1992 Z M17.6992,144.199
|
||||
C7.99922,144.199,0,152.1,0,161.9 L0,170.199
|
||||
C7.99922,144.199,2.36848e-15,152.1,0,161.9 L0,170.199
|
||||
C0,179.899,7.89922,187.9,17.6992,187.9 L40.1992,187.9 L40.1992,144.199
|
||||
L17.6992,144.199 Z M97.8008,144.199 L97.8008,187.9 L217.301,187.9
|
||||
C227.001,187.9,235,179.899,235,170.199 L235,161.9
|
||||
|
||||
@@ -1,42 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_large">
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="20dp" >
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/createForumNameLayout"
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:text="@string/choose_forum_name" />
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true">
|
||||
android:id="@+id/createForumNameEntry"
|
||||
android:maxLines="1"
|
||||
android:inputType="text|textCapSentences" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/createForumNameEntry"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/choose_forum_hint"
|
||||
android:inputType="text|textCapSentences"
|
||||
android:maxLines="1"/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/createForumFeedback"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="50dp"
|
||||
android:paddingRight="50dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/createForumButton"
|
||||
style="@style/BriarButton"
|
||||
android:enabled="false"
|
||||
android:text="@string/create_forum_button"/>
|
||||
android:id="@+id/createForumButton"
|
||||
android:text="@string/create_forum_button" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/createForumProgressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user