mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
133 Commits
test-immed
...
priority-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4d0a221a3 | ||
|
|
b342759e06 | ||
|
|
93d99b0111 | ||
|
|
61e8d576d2 | ||
|
|
75c37a258e | ||
|
|
e964dae64b | ||
|
|
986d884b40 | ||
|
|
9557afabc6 | ||
|
|
ebe6b0d4c0 | ||
|
|
6e83fb7aef | ||
|
|
7a5ec2af12 | ||
|
|
ce1fde496c | ||
|
|
4b62c51fbf | ||
|
|
226ed3dd73 | ||
|
|
ab07dfb32c | ||
|
|
20c51c1aa4 | ||
|
|
232c2129a7 | ||
|
|
3620edbfc9 | ||
|
|
ad71d69149 | ||
|
|
f73f8ca7e7 | ||
|
|
16c701a71a | ||
|
|
8183b7b26a | ||
|
|
bd48c97eab | ||
|
|
925dc29a1f | ||
|
|
91777fd942 | ||
|
|
fbce8f81c7 | ||
|
|
d7c72c4d68 | ||
|
|
4faf535801 | ||
|
|
526ef7c6d8 | ||
|
|
798dff1a03 | ||
|
|
a4336776c9 | ||
|
|
418451cbd9 | ||
|
|
045fcfc5fa | ||
|
|
ef998577db | ||
|
|
a53345a3c9 | ||
|
|
ed8c09282d | ||
|
|
42197b5b5c | ||
|
|
374fc7035b | ||
|
|
9b796c7cc3 | ||
|
|
532edff642 | ||
|
|
6857252471 | ||
|
|
c229e19452 | ||
|
|
42bca09d16 | ||
|
|
9eacbfa659 | ||
|
|
f14e546dc6 | ||
|
|
684c64a1d9 | ||
|
|
6fdab959b1 | ||
|
|
c8487483ff | ||
|
|
a159b23dc0 | ||
|
|
5070a27a83 | ||
|
|
9ce73a6840 | ||
|
|
6e9928f20f | ||
|
|
b31d61afc5 | ||
|
|
5a99cb93cc | ||
|
|
d0bbebd25e | ||
|
|
4307d26606 | ||
|
|
0089c1ac6d | ||
|
|
2a7aac4930 | ||
|
|
a37b6d81ed | ||
|
|
1d09a6708a | ||
|
|
d3b6f484c8 | ||
|
|
039c6edb66 | ||
|
|
8b9f89eab2 | ||
|
|
1e2c17b170 | ||
|
|
a994966095 | ||
|
|
2bea581654 | ||
|
|
87377666aa | ||
|
|
9d07b2e141 | ||
|
|
5c312b49e2 | ||
|
|
f56efe45cd | ||
|
|
2332a58681 | ||
|
|
8c6dfaa196 | ||
|
|
3cfb04b60d | ||
|
|
e85fbfb952 | ||
|
|
80ee35d926 | ||
|
|
4796902b9c | ||
|
|
149e67c0f7 | ||
|
|
1d5214117f | ||
|
|
b8f248ca9c | ||
|
|
dfb71a03a5 | ||
|
|
961fdc8e72 | ||
|
|
c3d44663cd | ||
|
|
0081472489 | ||
|
|
cdf4f3a24b | ||
|
|
fb1d8e860f | ||
|
|
a3c526ec9a | ||
|
|
dee488d06d | ||
|
|
b29c7d8022 | ||
|
|
0725d207ec | ||
|
|
5a7599a88d | ||
|
|
59cd98db81 | ||
|
|
768488eb04 | ||
|
|
a6b1ad48c3 | ||
|
|
77299a68ed | ||
|
|
5e5705c73b | ||
|
|
e6229a3a13 | ||
|
|
5fbacb4ee4 | ||
|
|
c7f4e976ed | ||
|
|
419f2d966a | ||
|
|
d6c18db9e9 | ||
|
|
8fe49d9961 | ||
|
|
f536cfdab8 | ||
|
|
4d594acad5 | ||
|
|
800dfed5c1 | ||
|
|
54b823e401 | ||
|
|
52ec56d690 | ||
|
|
7b3afcca99 | ||
|
|
a22d03d028 | ||
|
|
d857338ad0 | ||
|
|
a5c9e7c74d | ||
|
|
8a4a343147 | ||
|
|
dcd5e34c6b | ||
|
|
7b22d3b84d | ||
|
|
c8fa23273f | ||
|
|
fbe5df8938 | ||
|
|
008cf95741 | ||
|
|
3eb066a836 | ||
|
|
674b29af25 | ||
|
|
b8ca5ab557 | ||
|
|
6e17709f46 | ||
|
|
726d90145c | ||
|
|
165211eb9b | ||
|
|
868c61e5d6 | ||
|
|
798bb6d4f7 | ||
|
|
bc352a2dc6 | ||
|
|
ce7d6d3db5 | ||
|
|
61276c81d2 | ||
|
|
152ac3df43 | ||
|
|
dd5ad86db8 | ||
|
|
10e9fb308d | ||
|
|
de8e95692a | ||
|
|
d6b52cf4ec | ||
|
|
8a839fb5e4 |
@@ -12,7 +12,7 @@ test:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom test --continue
|
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom test
|
||||||
|
|
||||||
after_script:
|
after_script:
|
||||||
# these file change every time but should not be cached
|
# these file change every time but should not be cached
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ import javax.inject.Inject;
|
|||||||
import static android.content.Intent.ACTION_BATTERY_CHANGED;
|
import static android.content.Intent.ACTION_BATTERY_CHANGED;
|
||||||
import static android.content.Intent.ACTION_POWER_CONNECTED;
|
import static android.content.Intent.ACTION_POWER_CONNECTED;
|
||||||
import static android.content.Intent.ACTION_POWER_DISCONNECTED;
|
import static android.content.Intent.ACTION_POWER_DISCONNECTED;
|
||||||
import static android.os.BatteryManager.BATTERY_STATUS_CHARGING;
|
import static android.os.BatteryManager.EXTRA_PLUGGED;
|
||||||
import static android.os.BatteryManager.BATTERY_STATUS_FULL;
|
|
||||||
import static android.os.BatteryManager.EXTRA_STATUS;
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
@@ -48,9 +46,8 @@ class AndroidBatteryManager implements BatteryManager, Service {
|
|||||||
IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
|
IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
|
||||||
Intent i = appContext.registerReceiver(null, filter);
|
Intent i = appContext.registerReceiver(null, filter);
|
||||||
if (i == null) return false;
|
if (i == null) return false;
|
||||||
int status = i.getIntExtra(EXTRA_STATUS, -1);
|
int status = i.getIntExtra(EXTRA_PLUGGED, 0);
|
||||||
return status == BATTERY_STATUS_CHARGING ||
|
return status != 0;
|
||||||
status == BATTERY_STATUS_FULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -51,7 +51,6 @@ import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
|
|||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -161,11 +160,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void tryToClose(@Nullable BluetoothServerSocket ss) {
|
void tryToClose(@Nullable BluetoothServerSocket ss) {
|
||||||
try {
|
IoUtils.tryToClose(ss, LOG, WARNING);
|
||||||
if (ss != null) ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -195,7 +190,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
s.connect();
|
s.connect();
|
||||||
return wrapSocket(s);
|
return wrapSocket(s);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
tryToClose(s);
|
IoUtils.tryToClose(s, LOG, WARNING);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,14 +263,6 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
return addresses;
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Closeable c) {
|
|
||||||
try {
|
|
||||||
if (c != null) c.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.system;
|
package org.briarproject.bramble.system;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
@@ -26,7 +27,7 @@ import static android.provider.Settings.Secure.ANDROID_ID;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
|
class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
|
||||||
|
|
||||||
private static final int SEED_LENGTH = 32;
|
private static final int SEED_LENGTH = 32;
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
|
|||||||
appContext = app.getApplicationContext();
|
appContext = app.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("HardwareIds")
|
||||||
@Override
|
@Override
|
||||||
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
|
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||||
super.writeToEntropyPool(out);
|
super.writeToEntropyPool(out);
|
||||||
@@ -49,13 +51,15 @@ class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
|
|||||||
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
|
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
|
||||||
if (id != null) out.writeUTF(id);
|
if (id != null) out.writeUTF(id);
|
||||||
Parcel parcel = Parcel.obtain();
|
Parcel parcel = Parcel.obtain();
|
||||||
WifiManager wm =
|
WifiManager wm = (WifiManager) appContext.getApplicationContext()
|
||||||
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
|
.getSystemService(WIFI_SERVICE);
|
||||||
|
if (wm != null) {
|
||||||
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
|
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
|
||||||
if (configs != null) {
|
if (configs != null) {
|
||||||
for (WifiConfiguration config : configs)
|
for (WifiConfiguration config : configs)
|
||||||
parcel.writeParcelable(config, 0);
|
parcel.writeParcelable(config, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||||
if (bt != null) {
|
if (bt != null) {
|
||||||
for (BluetoothDevice device : bt.getBondedDevices())
|
for (BluetoothDevice device : bt.getBondedDevices())
|
||||||
@@ -77,13 +81,13 @@ class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
|
|||||||
|
|
||||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||||
private void applyOpenSslFix() {
|
private void applyOpenSslFix() {
|
||||||
byte[] seed = new LinuxSecureRandomSpi().engineGenerateSeed(
|
byte[] seed = new UnixSecureRandomSpi().engineGenerateSeed(
|
||||||
SEED_LENGTH);
|
SEED_LENGTH);
|
||||||
try {
|
try {
|
||||||
// Seed the OpenSSL PRNG
|
// Seed the OpenSSL PRNG
|
||||||
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||||
.getMethod("RAND_seed", byte[].class)
|
.getMethod("RAND_seed", byte[].class)
|
||||||
.invoke(null, seed);
|
.invoke(null, (Object) seed);
|
||||||
// Mix the output of the Linux PRNG into the OpenSSL PRNG
|
// Mix the output of the Linux PRNG into the OpenSSL PRNG
|
||||||
int bytesRead = (Integer) Class.forName(
|
int bytesRead = (Integer) Class.forName(
|
||||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.FAILED;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ContactManager {
|
public interface ContactManager {
|
||||||
|
|
||||||
@@ -52,6 +54,35 @@ public interface ContactManager {
|
|||||||
long timestamp, boolean alice, boolean verified, boolean active)
|
long timestamp, boolean alice, boolean verified, boolean active)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the static link that needs to be sent to the contact to be added.
|
||||||
|
*/
|
||||||
|
String getRemoteContactLink();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given link is syntactically valid.
|
||||||
|
*/
|
||||||
|
boolean isValidRemoteContactLink(String link);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a new contact to be added via the given {@code link}.
|
||||||
|
*
|
||||||
|
* @param link The link received from the contact we want to add.
|
||||||
|
* @param alias The alias the user has given this contact.
|
||||||
|
* @return A PendingContact representing the contact to be added.
|
||||||
|
*/
|
||||||
|
PendingContact addRemoteContactRequest(String link, String alias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of {@link PendingContact}s.
|
||||||
|
*/
|
||||||
|
Collection<PendingContact> getPendingContacts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a {@link PendingContact} that is in state {@link FAILED}.
|
||||||
|
*/
|
||||||
|
void removePendingContact(PendingContact pendingContact);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the contact with the given ID.
|
* Returns the contact with the given ID.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.briarproject.bramble.api.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class PendingContact {
|
||||||
|
|
||||||
|
public enum PendingContactState {
|
||||||
|
WAITING_FOR_CONNECTION,
|
||||||
|
CONNECTED,
|
||||||
|
ADDING_CONTACT,
|
||||||
|
FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PendingContactId id;
|
||||||
|
private final String alias;
|
||||||
|
private final PendingContactState state;
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
public PendingContact(PendingContactId id, String alias,
|
||||||
|
PendingContactState state, long timestamp) {
|
||||||
|
this.id = id;
|
||||||
|
this.alias = alias;
|
||||||
|
this.state = state;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PendingContactState getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof PendingContact &&
|
||||||
|
id.equals(((PendingContact) o).id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.briarproject.bramble.api.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
|
|
||||||
|
public class PendingContactId extends UniqueId {
|
||||||
|
|
||||||
|
public PendingContactId(byte[] id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.bramble.api.contact.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact.PendingContactState;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when a pending contact's state is changed.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class PendingContactStateChangedEvent extends Event {
|
||||||
|
|
||||||
|
private final PendingContactId id;
|
||||||
|
private final PendingContactState state;
|
||||||
|
|
||||||
|
public PendingContactStateChangedEvent(PendingContactId id,
|
||||||
|
PendingContactState state) {
|
||||||
|
this.id = id;
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PendingContactId getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PendingContactState getPendingContactState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ public interface TorConstants {
|
|||||||
String PREF_TOR_NETWORK = "network2";
|
String PREF_TOR_NETWORK = "network2";
|
||||||
String PREF_TOR_PORT = "port";
|
String PREF_TOR_PORT = "port";
|
||||||
String PREF_TOR_MOBILE = "useMobileData";
|
String PREF_TOR_MOBILE = "useMobileData";
|
||||||
|
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
|
||||||
|
|
||||||
int PREF_TOR_NETWORK_AUTOMATIC = 0;
|
int PREF_TOR_NETWORK_AUTOMATIC = 0;
|
||||||
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
|
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ public interface ClientVersioningManager {
|
|||||||
Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
||||||
ClientId clientId, int majorVersion) throws DbException;
|
ClientId clientId, int majorVersion) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minor version of the given client that is supported by the
|
||||||
|
* given contact, or -1 if the contact does not support the client.
|
||||||
|
*/
|
||||||
|
int getClientMinorVersion(Transaction txn, ContactId contactId,
|
||||||
|
ClientId clientId, int majorVersion) throws DbException;
|
||||||
|
|
||||||
interface ClientVersioningHook {
|
interface ClientVersioningHook {
|
||||||
|
|
||||||
void onClientVisibilityChanging(Transaction txn, Contact c,
|
void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||||
|
|||||||
@@ -8,12 +8,15 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class IoUtils {
|
public class IoUtils {
|
||||||
@@ -54,16 +57,35 @@ public class IoUtils {
|
|||||||
out.flush();
|
out.flush();
|
||||||
out.close();
|
out.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
tryToClose(in);
|
tryToClose(in, LOG, WARNING);
|
||||||
tryToClose(out);
|
tryToClose(out, LOG, WARNING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void tryToClose(@Nullable Closeable c) {
|
public static void tryToClose(@Nullable Closeable c, Logger logger,
|
||||||
|
Level level) {
|
||||||
try {
|
try {
|
||||||
if (c != null) c.close();
|
if (c != null) c.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// We did our best
|
logException(logger, level, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tryToClose(@Nullable Socket s, Logger logger,
|
||||||
|
Level level) {
|
||||||
|
try {
|
||||||
|
if (s != null) s.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(logger, level, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tryToClose(@Nullable ServerSocket ss, Logger logger,
|
||||||
|
Level level) {
|
||||||
|
try {
|
||||||
|
if (ss != null) ss.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(logger, level, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ public class OsUtils {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private static final String os = System.getProperty("os.name");
|
private static final String os = System.getProperty("os.name");
|
||||||
@Nullable
|
@Nullable
|
||||||
private static final String version = System.getProperty("os.version");
|
|
||||||
@Nullable
|
|
||||||
private static final String vendor = System.getProperty("java.vendor");
|
private static final String vendor = System.getProperty("java.vendor");
|
||||||
|
|
||||||
public static boolean isWindows() {
|
public static boolean isWindows() {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class PriorityExecutor {
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
private final Executor delegate, high, low;
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private final Queue<Runnable> highQueue = new LinkedList<>();
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private final Queue<Runnable> lowQueue = new LinkedList<>();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private boolean isTaskRunning = false;
|
||||||
|
|
||||||
|
public PriorityExecutor(Executor delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
high = r -> submit(r, true);
|
||||||
|
low = r -> submit(r, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Executor getHighPriorityExecutor() {
|
||||||
|
return high;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Executor getLowPriorityExecutor() {
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submit(Runnable r, boolean isHighPriority) {
|
||||||
|
Runnable wrapped = () -> {
|
||||||
|
try {
|
||||||
|
r.run();
|
||||||
|
} finally {
|
||||||
|
scheduleNext();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
synchronized (lock) {
|
||||||
|
if (!isTaskRunning && highQueue.isEmpty() &&
|
||||||
|
(isHighPriority || lowQueue.isEmpty())) {
|
||||||
|
isTaskRunning = true;
|
||||||
|
delegate.execute(wrapped);
|
||||||
|
} else if (isHighPriority) {
|
||||||
|
highQueue.add(wrapped);
|
||||||
|
} else {
|
||||||
|
lowQueue.add(wrapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleNext() {
|
||||||
|
synchronized (lock) {
|
||||||
|
Runnable next = highQueue.poll();
|
||||||
|
if (next == null) next = lowQueue.poll();
|
||||||
|
if (next == null) isTaskRunning = false;
|
||||||
|
else delegate.execute(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package org.briarproject.bramble.contact;
|
|||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
@@ -19,12 +21,16 @@ import org.briarproject.bramble.api.transport.KeyManager;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||||
@@ -36,6 +42,12 @@ import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ContactManagerImpl implements ContactManager {
|
class ContactManagerImpl implements ContactManager {
|
||||||
|
|
||||||
|
private static final int LINK_LENGTH = 64;
|
||||||
|
private static final String REMOTE_CONTACT_LINK =
|
||||||
|
"briar://" + getRandomBase32String(LINK_LENGTH);
|
||||||
|
private static final Pattern LINK_REGEX =
|
||||||
|
Pattern.compile("(briar://)?([a-z2-7]{" + LINK_LENGTH + "})");
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final KeyManager keyManager;
|
private final KeyManager keyManager;
|
||||||
private final IdentityManager identityManager;
|
private final IdentityManager identityManager;
|
||||||
@@ -84,6 +96,48 @@ class ContactManagerImpl implements ContactManager {
|
|||||||
verified, active));
|
verified, active));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRemoteContactLink() {
|
||||||
|
// TODO replace with real implementation
|
||||||
|
return REMOTE_CONTACT_LINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private static String getRandomBase32String(int length) {
|
||||||
|
Random random = new Random();
|
||||||
|
char[] c = new char[length];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int character = random.nextInt(32);
|
||||||
|
if (character < 26) c[i] = (char) ('a' + character);
|
||||||
|
else c[i] = (char) ('2' + (character - 26));
|
||||||
|
}
|
||||||
|
return new String(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidRemoteContactLink(String link) {
|
||||||
|
return LINK_REGEX.matcher(link).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PendingContact addRemoteContactRequest(String link, String alias) {
|
||||||
|
// TODO replace with real implementation
|
||||||
|
PendingContactId id = new PendingContactId(link.getBytes());
|
||||||
|
return new PendingContact(id, alias, WAITING_FOR_CONNECTION,
|
||||||
|
System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<PendingContact> getPendingContacts() {
|
||||||
|
// TODO replace with real implementation
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removePendingContact(PendingContact pendingContact) {
|
||||||
|
// TODO replace with real implementation
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Contact getContact(ContactId c) throws DbException {
|
public Contact getContact(ContactId c) throws DbException {
|
||||||
return db.transactionWithResult(true, txn -> db.getContact(txn, c));
|
return db.transactionWithResult(true, txn -> db.getContact(txn, c));
|
||||||
|
|||||||
@@ -15,16 +15,23 @@ import java.sql.DriverManager;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains all the H2-specific code for the database.
|
* Contains all the H2-specific code for the database.
|
||||||
*/
|
*/
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class H2Database extends JdbcDatabase {
|
class H2Database extends JdbcDatabase {
|
||||||
|
|
||||||
|
private static final Logger LOG = getLogger(H2Database.class.getName());
|
||||||
|
|
||||||
private static final String HASH_TYPE = "BINARY(32)";
|
private static final String HASH_TYPE = "BINARY(32)";
|
||||||
private static final String SECRET_TYPE = "BINARY(32)";
|
private static final String SECRET_TYPE = "BINARY(32)";
|
||||||
private static final String BINARY_TYPE = "BINARY";
|
private static final String BINARY_TYPE = "BINARY";
|
||||||
@@ -121,8 +128,8 @@ class H2Database extends JdbcDatabase {
|
|||||||
s.close();
|
s.close();
|
||||||
c.close();
|
c.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
tryToClose(c);
|
tryToClose(c, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,24 @@ import java.sql.Connection;
|
|||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains all the HSQLDB-specific code for the database.
|
* Contains all the HSQLDB-specific code for the database.
|
||||||
*/
|
*/
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class HyperSqlDatabase extends JdbcDatabase {
|
class HyperSqlDatabase extends JdbcDatabase {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(HyperSqlDatabase.class.getName());
|
||||||
|
|
||||||
private static final String HASH_TYPE = "BINARY(32)";
|
private static final String HASH_TYPE = "BINARY(32)";
|
||||||
private static final String SECRET_TYPE = "BINARY(32)";
|
private static final String SECRET_TYPE = "BINARY(32)";
|
||||||
private static final String BINARY_TYPE = "BINARY";
|
private static final String BINARY_TYPE = "BINARY";
|
||||||
@@ -72,8 +80,8 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
s.close();
|
s.close();
|
||||||
c.close();
|
c.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
tryToClose(c);
|
tryToClose(c, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,8 +130,8 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
s.close();
|
s.close();
|
||||||
c.close();
|
c.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
tryToClose(c);
|
tryToClose(c, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
|
|||||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
|
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||||
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||||
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
@@ -458,30 +459,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable ResultSet rs) {
|
|
||||||
try {
|
|
||||||
if (rs != null) rs.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void tryToClose(@Nullable Statement s) {
|
|
||||||
try {
|
|
||||||
if (s != null) s.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void tryToClose(@Nullable Connection c) {
|
|
||||||
try {
|
|
||||||
if (c != null) c.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createTables(Connection txn) throws DbException {
|
private void createTables(Connection txn) throws DbException {
|
||||||
Statement s = null;
|
Statement s = null;
|
||||||
try {
|
try {
|
||||||
@@ -502,7 +479,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS));
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,7 +496,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,11 +543,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
// Try to close the connection
|
// Try to close the connection
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
try {
|
tryToClose(txn, LOG, WARNING);
|
||||||
txn.close();
|
|
||||||
} catch (SQLException e1) {
|
|
||||||
logException(LOG, WARNING, e1);
|
|
||||||
}
|
|
||||||
// Whatever happens, allow the database to close
|
// Whatever happens, allow the database to close
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
@@ -659,8 +632,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return c;
|
return c;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -681,7 +654,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -704,7 +677,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
// Create a status row for each message in the group
|
// Create a status row for each message in the group
|
||||||
addStatus(txn, c, g, groupShared);
|
addStatus(txn, c, g, groupShared);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -735,8 +708,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -761,7 +734,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -810,7 +783,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -840,8 +813,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -873,7 +846,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -915,8 +888,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -935,7 +908,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1015,8 +988,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return keySetId;
|
return keySetId;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1039,8 +1012,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return found;
|
return found;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1061,8 +1034,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return found;
|
return found;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1083,8 +1056,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return found;
|
return found;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1105,8 +1078,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return found;
|
return found;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1127,8 +1100,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return found;
|
return found;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1149,8 +1122,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return found;
|
return found;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1174,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return found;
|
return found;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1198,8 +1171,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return count;
|
return count;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1223,7 +1196,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1240,7 +1213,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1273,8 +1246,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
return new Contact(c, author, localAuthorId, alias, verified,
|
return new Contact(c, author, localAuthorId, alias, verified,
|
||||||
active);
|
active);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1310,8 +1283,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return contacts;
|
return contacts;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1333,8 +1306,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1371,8 +1344,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return contacts;
|
return contacts;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1395,8 +1368,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return new Group(g, clientId, majorVersion, descriptor);
|
return new Group(g, clientId, majorVersion, descriptor);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1423,8 +1396,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return groups;
|
return groups;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1449,8 +1422,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return v;
|
return v;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1473,8 +1446,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return visible;
|
return visible;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1505,8 +1478,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return localAuthor;
|
return localAuthor;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1537,8 +1510,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return authors;
|
return authors;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1566,8 +1539,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
|
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
|
||||||
return new Message(m, g, timestamp, body);
|
return new Message(m, g, timestamp, body);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1590,8 +1563,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1628,8 +1601,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (intersection == null) throw new AssertionError();
|
if (intersection == null) throw new AssertionError();
|
||||||
return intersection;
|
return intersection;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1661,8 +1634,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return all;
|
return all;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1696,8 +1669,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return metadata;
|
return metadata;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1720,8 +1693,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return metadata;
|
return metadata;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1746,8 +1719,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return metadata;
|
return metadata;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1776,8 +1749,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return statuses;
|
return statuses;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1807,8 +1780,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return status;
|
return status;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1837,8 +1810,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return dependencies;
|
return dependencies;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1868,8 +1841,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return dependents;
|
return dependents;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1891,8 +1864,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return state;
|
return state;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1916,8 +1889,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1950,8 +1923,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1975,8 +1948,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2014,8 +1987,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2048,8 +2021,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2076,8 +2049,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2106,8 +2079,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return nextSendTime;
|
return nextSendTime;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2145,8 +2118,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2168,8 +2141,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return s;
|
return s;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2234,8 +2207,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return keys;
|
return keys;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2254,7 +2227,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2281,7 +2254,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2308,7 +2281,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2338,7 +2311,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows != 1) throw new DbStateException();
|
if (rows != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2383,8 +2356,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows != 1) throw new DbStateException();
|
if (rows != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2450,7 +2423,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
return added;
|
return added;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2497,7 +2470,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows != 1) throw new DbStateException();
|
if (rows != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2516,7 +2489,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2535,7 +2508,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2554,7 +2527,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2571,7 +2544,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2587,7 +2560,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2615,7 +2588,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2632,7 +2605,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2648,7 +2621,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2667,7 +2640,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
return affected == 1;
|
return affected == 1;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2692,7 +2665,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows != 1) throw new DbStateException();
|
if (rows != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2709,7 +2682,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2730,7 +2703,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2749,7 +2722,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2767,7 +2740,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2785,7 +2758,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2804,7 +2777,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2834,7 +2807,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2860,7 +2833,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2913,7 +2886,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2936,7 +2909,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2955,7 +2928,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2991,8 +2964,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3059,7 +3032,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows < 0 || rows > 1) throw new DbStateException();
|
if (rows < 0 || rows > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class JdbcUtils {
|
||||||
|
|
||||||
|
static void tryToClose(@Nullable ResultSet rs, Logger logger, Level level) {
|
||||||
|
try {
|
||||||
|
if (rs != null) rs.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logException(logger, level, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tryToClose(@Nullable Statement s, Logger logger, Level level) {
|
||||||
|
try {
|
||||||
|
if (s != null) s.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logException(logger, level, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tryToClose(@Nullable Connection c, Logger logger, Level level) {
|
||||||
|
try {
|
||||||
|
if (c != null) c.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logException(logger, level, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,8 @@ import java.sql.SQLException;
|
|||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
|
|
||||||
class Migration38_39 implements Migration<Connection> {
|
class Migration38_39 implements Migration<Connection> {
|
||||||
|
|
||||||
@@ -40,16 +38,8 @@ class Migration38_39 implements Migration<Connection> {
|
|||||||
+ " ALTER COLUMN contactId"
|
+ " ALTER COLUMN contactId"
|
||||||
+ " SET NOT NULL");
|
+ " SET NOT NULL");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Statement s) {
|
|
||||||
try {
|
|
||||||
if (s != null) s.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,8 @@ import java.sql.SQLException;
|
|||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
|
|
||||||
class Migration39_40 implements Migration<Connection> {
|
class Migration39_40 implements Migration<Connection> {
|
||||||
|
|
||||||
@@ -39,16 +37,8 @@ class Migration39_40 implements Migration<Connection> {
|
|||||||
+ " ALTER COLUMN eta"
|
+ " ALTER COLUMN eta"
|
||||||
+ " SET NOT NULL");
|
+ " SET NOT NULL");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Statement s) {
|
|
||||||
try {
|
|
||||||
if (s != null) s.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import java.sql.SQLException;
|
|||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
|
|
||||||
class Migration40_41 implements Migration<Connection> {
|
class Migration40_41 implements Migration<Connection> {
|
||||||
|
|
||||||
@@ -41,16 +39,8 @@ class Migration40_41 implements Migration<Connection> {
|
|||||||
s.execute("ALTER TABLE contacts"
|
s.execute("ALTER TABLE contacts"
|
||||||
+ dbTypes.replaceTypes(" ADD alias _STRING"));
|
+ dbTypes.replaceTypes(" ADD alias _STRING"));
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Statement s) {
|
|
||||||
try {
|
|
||||||
if (s != null) s.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class FileTransportReader implements TransportConnectionReader {
|
class FileTransportReader implements TransportConnectionReader {
|
||||||
@@ -34,11 +33,7 @@ class FileTransportReader implements TransportConnectionReader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose(boolean exception, boolean recognised) {
|
public void dispose(boolean exception, boolean recognised) {
|
||||||
try {
|
tryToClose(in, LOG, WARNING);
|
||||||
in.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
plugin.readerFinished(file, exception, recognised);
|
plugin.readerFinished(file, exception, recognised);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class FileTransportWriter implements TransportConnectionWriter {
|
class FileTransportWriter implements TransportConnectionWriter {
|
||||||
@@ -44,11 +43,7 @@ class FileTransportWriter implements TransportConnectionWriter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose(boolean exception) {
|
public void dispose(boolean exception) {
|
||||||
try {
|
tryToClose(out, LOG, WARNING);
|
||||||
out.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
plugin.writerFinished(file, exception);
|
plugin.writerFinished(file, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -35,7 +36,6 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
|
|||||||
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
|
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
|
||||||
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
|
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -293,11 +293,7 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
try {
|
IoUtils.tryToClose(ss, LOG, WARNING);
|
||||||
ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -153,14 +154,9 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void tryToClose(@Nullable ServerSocket ss) {
|
protected void tryToClose(@Nullable ServerSocket ss) {
|
||||||
try {
|
IoUtils.tryToClose(ss, LOG, WARNING);
|
||||||
if (ss != null) ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
} finally {
|
|
||||||
callback.transportDisabled();
|
callback.transportDisabled();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
String getIpPortString(InetSocketAddress a) {
|
String getIpPortString(InetSocketAddress a) {
|
||||||
String addr = a.getAddress().getHostAddress();
|
String addr = a.getAddress().getHostAddress();
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
import org.briarproject.bramble.util.IoUtils;
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@@ -70,6 +69,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
|||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
|
||||||
|
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
||||||
@@ -303,8 +303,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
IoUtils.copyAndClose(in, out);
|
IoUtils.copyAndClose(in, out);
|
||||||
doneFile.createNewFile();
|
doneFile.createNewFile();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
tryToClose(in);
|
IoUtils.tryToClose(in, LOG, WARNING);
|
||||||
tryToClose(out);
|
IoUtils.tryToClose(out, LOG, WARNING);
|
||||||
throw new PluginException(e);
|
throw new PluginException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,22 +341,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
return getClass().getClassLoader().getResourceAsStream("torrc");
|
return getClass().getClassLoader().getResourceAsStream("torrc");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Closeable c) {
|
|
||||||
try {
|
|
||||||
if (c != null) c.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable Socket s) {
|
|
||||||
try {
|
|
||||||
if (s != null) s.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void listFiles(File f) {
|
private void listFiles(File f) {
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
File[] children = f.listFiles();
|
File[] children = f.listFiles();
|
||||||
@@ -378,7 +362,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
return b;
|
return b;
|
||||||
} finally {
|
} finally {
|
||||||
tryToClose(in);
|
IoUtils.tryToClose(in, LOG, WARNING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,14 +402,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable ServerSocket ss) {
|
private void tryToClose(@Nullable ServerSocket ss) {
|
||||||
try {
|
IoUtils.tryToClose(ss, LOG, WARNING);
|
||||||
if (ss != null) ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
} finally {
|
|
||||||
callback.transportDisabled();
|
callback.transportDisabled();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void publishHiddenService(String port) {
|
private void publishHiddenService(String port) {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
@@ -593,7 +572,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
LOG.info("Could not connect to " + scrubOnion(bestOnion)
|
LOG.info("Could not connect to " + scrubOnion(bestOnion)
|
||||||
+ ": " + e.toString());
|
+ ": " + e.toString());
|
||||||
}
|
}
|
||||||
tryToClose(s);
|
IoUtils.tryToClose(s, LOG, WARNING);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,8 +649,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (s.getNamespace().equals(ID.getString())) {
|
if (s.getNamespace().equals(ID.getString())) {
|
||||||
LOG.info("Tor settings updated");
|
LOG.info("Tor settings updated");
|
||||||
settings = s.getSettings();
|
settings = s.getSettings();
|
||||||
// Works around a bug introduced in Tor 0.3.4.8. Could be
|
// Works around a bug introduced in Tor 0.3.4.8.
|
||||||
// replaced with callback.transportDisabled() when fixed.
|
// https://trac.torproject.org/projects/tor/ticket/28027
|
||||||
|
// Could be replaced with callback.transportDisabled()
|
||||||
|
// when fixed.
|
||||||
disableNetwork();
|
disableNetwork();
|
||||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||||
batteryManager.isCharging());
|
batteryManager.isCharging());
|
||||||
@@ -707,6 +688,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
int network = settings.getInt(PREF_TOR_NETWORK,
|
int network = settings.getInt(PREF_TOR_NETWORK,
|
||||||
PREF_TOR_NETWORK_AUTOMATIC);
|
PREF_TOR_NETWORK_AUTOMATIC);
|
||||||
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
|
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
|
||||||
|
boolean onlyWhenCharging =
|
||||||
|
settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
|
||||||
boolean bridgesWork = circumventionProvider.doBridgesWork(country);
|
boolean bridgesWork = circumventionProvider.doBridgesWork(country);
|
||||||
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
|
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
|
||||||
|
|
||||||
@@ -721,9 +704,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (!online) {
|
if (!online) {
|
||||||
LOG.info("Disabling network, device is offline");
|
LOG.info("Disabling network, device is offline");
|
||||||
enableNetwork(false);
|
enableNetwork(false);
|
||||||
|
} else if (!charging && onlyWhenCharging) {
|
||||||
|
LOG.info("Disabling network, device is on battery");
|
||||||
|
enableNetwork(false);
|
||||||
} else if (network == PREF_TOR_NETWORK_NEVER ||
|
} else if (network == PREF_TOR_NETWORK_NEVER ||
|
||||||
(!useMobile && !wifi)) {
|
(!useMobile && !wifi)) {
|
||||||
LOG.info("Disabling network due to setting");
|
LOG.info("Disabling network, device is using mobile data");
|
||||||
enableNetwork(false);
|
enableNetwork(false);
|
||||||
} else if (automatic && blocked && !bridgesWork) {
|
} else if (automatic && blocked && !bridgesWork) {
|
||||||
LOG.info("Disabling network, country is blocked");
|
LOG.info("Disabling network, country is blocked");
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.reporting.DevReporter;
|
|||||||
import org.briarproject.bramble.util.IoUtils;
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@@ -26,13 +25,12 @@ import java.net.Socket;
|
|||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -66,7 +64,7 @@ class DevReporterImpl implements DevReporter, EventListener {
|
|||||||
s.setSoTimeout(SOCKET_TIMEOUT);
|
s.setSoTimeout(SOCKET_TIMEOUT);
|
||||||
return s;
|
return s;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
tryToClose(s);
|
tryToClose(s, LOG, WARNING);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,8 +86,7 @@ class DevReporterImpl implements DevReporter, EventListener {
|
|||||||
writer.append(armoured);
|
writer.append(armoured);
|
||||||
writer.flush();
|
writer.flush();
|
||||||
} finally {
|
} finally {
|
||||||
if (writer != null)
|
tryToClose(writer, LOG, WARNING);
|
||||||
writer.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,27 +118,11 @@ class DevReporterImpl implements DevReporter, EventListener {
|
|||||||
f.delete();
|
f.delete();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.log(WARNING, "Failed to send reports", e);
|
LOG.log(WARNING, "Failed to send reports", e);
|
||||||
tryToClose(out);
|
tryToClose(out, LOG, WARNING);
|
||||||
tryToClose(in);
|
tryToClose(in, LOG, WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG.info("Reports sent");
|
LOG.info("Reports sent");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Closeable c) {
|
|
||||||
try {
|
|
||||||
if (c != null) c.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable Socket s) {
|
|
||||||
try {
|
|
||||||
if (s != null) s.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,32 +13,33 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class LinuxSecureRandomProvider extends AbstractSecureRandomProvider {
|
class UnixSecureRandomProvider extends AbstractSecureRandomProvider {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LinuxSecureRandomProvider.class.getName());
|
getLogger(UnixSecureRandomProvider.class.getName());
|
||||||
|
|
||||||
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
||||||
|
|
||||||
private final AtomicBoolean seeded = new AtomicBoolean(false);
|
private final AtomicBoolean seeded = new AtomicBoolean(false);
|
||||||
private final File outputDevice;
|
private final File outputDevice;
|
||||||
|
|
||||||
LinuxSecureRandomProvider() {
|
UnixSecureRandomProvider() {
|
||||||
this(RANDOM_DEVICE);
|
this(RANDOM_DEVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
LinuxSecureRandomProvider(File outputDevice) {
|
UnixSecureRandomProvider(File outputDevice) {
|
||||||
this.outputDevice = outputDevice;
|
this.outputDevice = outputDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Provider getProvider() {
|
public Provider getProvider() {
|
||||||
if (!seeded.getAndSet(true)) writeSeed();
|
if (!seeded.getAndSet(true)) writeSeed();
|
||||||
return new LinuxProvider();
|
return new UnixProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeSeed() {
|
protected void writeSeed() {
|
||||||
@@ -55,15 +56,15 @@ class LinuxSecureRandomProvider extends AbstractSecureRandomProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||||
private static class LinuxProvider extends Provider {
|
private static class UnixProvider extends Provider {
|
||||||
|
|
||||||
private LinuxProvider() {
|
private UnixProvider() {
|
||||||
super("LinuxPRNG", 1.1, "A Linux-specific PRNG using /dev/urandom");
|
super("UnixPRNG", 1.0, "A Unix-specific PRNG using /dev/urandom");
|
||||||
// Although /dev/urandom is not a SHA-1 PRNG, some callers
|
// Although /dev/urandom is not a SHA-1 PRNG, some callers
|
||||||
// explicitly request a SHA1PRNG SecureRandom and we need to
|
// explicitly request a SHA1PRNG SecureRandom and we need to
|
||||||
// prevent them from getting the default implementation whose
|
// prevent them from getting the default implementation whose
|
||||||
// output may have low entropy.
|
// output may have low entropy.
|
||||||
put("SecureRandom.SHA1PRNG", LinuxSecureRandomSpi.class.getName());
|
put("SecureRandom.SHA1PRNG", UnixSecureRandomSpi.class.getName());
|
||||||
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,22 +10,24 @@ import java.security.SecureRandomSpi;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
public class LinuxSecureRandomSpi extends SecureRandomSpi {
|
public class UnixSecureRandomSpi extends SecureRandomSpi {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LinuxSecureRandomSpi.class.getName());
|
getLogger(UnixSecureRandomSpi.class.getName());
|
||||||
|
|
||||||
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
||||||
|
|
||||||
private final File inputDevice, outputDevice;
|
private final File inputDevice, outputDevice;
|
||||||
|
|
||||||
public LinuxSecureRandomSpi() {
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public UnixSecureRandomSpi() {
|
||||||
this(RANDOM_DEVICE, RANDOM_DEVICE);
|
this(RANDOM_DEVICE, RANDOM_DEVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
LinuxSecureRandomSpi(File inputDevice, File outputDevice) {
|
UnixSecureRandomSpi(File inputDevice, File outputDevice) {
|
||||||
this.inputDevice = inputDevice;
|
this.inputDevice = inputDevice;
|
||||||
this.outputDevice = outputDevice;
|
this.outputDevice = outputDevice;
|
||||||
}
|
}
|
||||||
@@ -89,14 +89,9 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
|||||||
public Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
public Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
||||||
ClientId clientId, int majorVersion) throws DbException {
|
ClientId clientId, int majorVersion) throws DbException {
|
||||||
try {
|
try {
|
||||||
Contact contact = db.getContact(txn, contactId);
|
LatestUpdates latest = findLatestUpdates(txn, contactId);
|
||||||
Group g = getContactGroup(contact);
|
if (latest == null || latest.remote == null) return INVISIBLE;
|
||||||
// Contact may be in the process of being added or removed, so
|
|
||||||
// contact group may not exist
|
|
||||||
if (!db.containsGroup(txn, g.getId())) return INVISIBLE;
|
|
||||||
LatestUpdates latest = findLatestUpdates(txn, g.getId());
|
|
||||||
if (latest.local == null) throw new DbException();
|
if (latest.local == null) throw new DbException();
|
||||||
if (latest.remote == null) return INVISIBLE;
|
|
||||||
Update localUpdate = loadUpdate(txn, latest.local.messageId);
|
Update localUpdate = loadUpdate(txn, latest.local.messageId);
|
||||||
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
|
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
|
||||||
Map<ClientMajorVersion, Visibility> visibilities =
|
Map<ClientMajorVersion, Visibility> visibilities =
|
||||||
@@ -110,6 +105,24 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getClientMinorVersion(Transaction txn, ContactId contactId,
|
||||||
|
ClientId clientId, int majorVersion) throws DbException {
|
||||||
|
try {
|
||||||
|
LatestUpdates latest = findLatestUpdates(txn, contactId);
|
||||||
|
if (latest == null || latest.remote == null) return -1;
|
||||||
|
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
|
||||||
|
ClientMajorVersion cv =
|
||||||
|
new ClientMajorVersion(clientId, majorVersion);
|
||||||
|
for (ClientState remote : remoteUpdate.states) {
|
||||||
|
if (remote.majorVersion.equals(cv)) return remote.minorVersion;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createLocalState(Transaction txn) throws DbException {
|
public void createLocalState(Transaction txn) throws DbException {
|
||||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||||
@@ -336,6 +349,17 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
|||||||
MAJOR_VERSION, c);
|
MAJOR_VERSION, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private LatestUpdates findLatestUpdates(Transaction txn, ContactId c)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
Contact contact = db.getContact(txn, c);
|
||||||
|
Group g = getContactGroup(contact);
|
||||||
|
// Contact may be in the process of being added or removed, so
|
||||||
|
// contact group may not exist
|
||||||
|
if (!db.containsGroup(txn, g.getId())) return null;
|
||||||
|
return findLatestUpdates(txn, g.getId());
|
||||||
|
}
|
||||||
|
|
||||||
private LatestUpdates findLatestUpdates(Transaction txn, GroupId g)
|
private LatestUpdates findLatestUpdates(Transaction txn, GroupId g)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
Map<MessageId, BdfDictionary> metadata =
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Vector;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.concurrent.Executors.newSingleThreadExecutor;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class PriorityExecutorTest extends BrambleTestCase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHighPriorityTasksAreDelegatedInOrderOfSubmission()
|
||||||
|
throws Exception {
|
||||||
|
Executor delegate = newSingleThreadExecutor();
|
||||||
|
PriorityExecutor priority = new PriorityExecutor(delegate);
|
||||||
|
Executor high = priority.getHighPriorityExecutor();
|
||||||
|
testTasksAreDelegatedInOrderOfSubmission(high);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLowPriorityTasksAreDelegatedInOrderOfSubmission()
|
||||||
|
throws Exception {
|
||||||
|
Executor delegate = newSingleThreadExecutor();
|
||||||
|
PriorityExecutor priority = new PriorityExecutor(delegate);
|
||||||
|
Executor low = priority.getLowPriorityExecutor();
|
||||||
|
testTasksAreDelegatedInOrderOfSubmission(low);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHighPriorityTasksAreRunFirst() throws Exception {
|
||||||
|
Executor delegate = newSingleThreadExecutor();
|
||||||
|
PriorityExecutor priority = new PriorityExecutor(delegate);
|
||||||
|
Executor high = priority.getHighPriorityExecutor();
|
||||||
|
Executor low = priority.getLowPriorityExecutor();
|
||||||
|
// Submit a task that will block, causing other tasks to be queued
|
||||||
|
CountDownLatch cork = new CountDownLatch(1);
|
||||||
|
low.execute(() -> {
|
||||||
|
try {
|
||||||
|
cork.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Submit alternating tasks to the high and low priority executors
|
||||||
|
List<Integer> results = new Vector<>();
|
||||||
|
CountDownLatch tasksFinished = new CountDownLatch(10);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
int result = i;
|
||||||
|
Runnable task = () -> {
|
||||||
|
results.add(result);
|
||||||
|
tasksFinished.countDown();
|
||||||
|
};
|
||||||
|
if (i % 2 == 0) high.execute(task);
|
||||||
|
else low.execute(task);
|
||||||
|
}
|
||||||
|
// Release the cork and wait for all tasks to finish
|
||||||
|
cork.countDown();
|
||||||
|
tasksFinished.await();
|
||||||
|
// The high-priority tasks should have run before the low-priority tasks
|
||||||
|
assertEquals(asList(0, 2, 4, 6, 8, 1, 3, 5, 7, 9), results);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testTasksAreDelegatedInOrderOfSubmission(Executor e)
|
||||||
|
throws Exception {
|
||||||
|
List<Integer> results = new Vector<>();
|
||||||
|
CountDownLatch tasksFinished = new CountDownLatch(10);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
int result = i;
|
||||||
|
e.execute(() -> {
|
||||||
|
results.add(result);
|
||||||
|
tasksFinished.countDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Wait for all the tasks to finish
|
||||||
|
tasksFinished.await();
|
||||||
|
// The tasks should have run in the order they were submitted
|
||||||
|
assertEquals(asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), results);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.briarproject.bramble.system;
|
package org.briarproject.bramble.system;
|
||||||
|
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.briarproject.bramble.util.OsUtils;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -10,48 +8,50 @@ import org.junit.Test;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||||
|
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
||||||
|
import static org.briarproject.bramble.util.OsUtils.isMac;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
public class LinuxSecureRandomProviderTest extends BrambleTestCase {
|
public class UnixSecureRandomProviderTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final File testDir = TestUtils.getTestDirectory();
|
private final File testDir = getTestDirectory();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
testDir.mkdirs();
|
assumeTrue(isLinux() || isMac());
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetProviderWritesToRandomDeviceOnFirstCall()
|
public void testGetProviderWritesToRandomDeviceOnFirstCall()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
if (!(OsUtils.isLinux())) {
|
|
||||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Redirect the provider's output to a file
|
// Redirect the provider's output to a file
|
||||||
File urandom = new File(testDir, "urandom");
|
File urandom = new File(testDir, "urandom");
|
||||||
urandom.delete();
|
if (urandom.exists()) assertTrue(urandom.delete());
|
||||||
assertTrue(urandom.createNewFile());
|
assertTrue(urandom.createNewFile());
|
||||||
assertEquals(0, urandom.length());
|
assertEquals(0, urandom.length());
|
||||||
LinuxSecureRandomProvider p = new LinuxSecureRandomProvider(urandom);
|
UnixSecureRandomProvider p = new UnixSecureRandomProvider(urandom);
|
||||||
// Getting a provider should write entropy to the file
|
// Getting a provider should write entropy to the file
|
||||||
Provider provider = p.getProvider();
|
Provider provider = p.getProvider();
|
||||||
assertNotNull(provider);
|
assertNotNull(provider);
|
||||||
assertEquals("LinuxPRNG", provider.getName());
|
assertEquals("UnixPRNG", provider.getName());
|
||||||
// There should be at least 16 bytes from the clock, 8 from the runtime
|
// There should be at least 16 bytes from the clock, 8 from the runtime
|
||||||
long length = urandom.length();
|
long length = urandom.length();
|
||||||
assertTrue(length >= 24);
|
assertTrue(length >= 24);
|
||||||
// Getting another provider should not write to the file again
|
// Getting another provider should not write to the file again
|
||||||
provider = p.getProvider();
|
provider = p.getProvider();
|
||||||
assertNotNull(provider);
|
assertNotNull(provider);
|
||||||
assertEquals("LinuxPRNG", provider.getName());
|
assertEquals("UnixPRNG", provider.getName());
|
||||||
assertEquals(length, urandom.length());
|
assertEquals(length, urandom.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectory(testDir);
|
deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.Bytes;
|
|||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.briarproject.bramble.util.IoUtils;
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
import org.briarproject.bramble.util.OsUtils;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -15,31 +14,33 @@ import java.io.FileOutputStream;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||||
|
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
||||||
|
import static org.briarproject.bramble.util.OsUtils.isMac;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
|
||||||
public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
public class UnixSecureRandomSpiTest extends BrambleTestCase {
|
||||||
|
|
||||||
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
||||||
private static final int SEED_BYTES = 32;
|
private static final int SEED_BYTES = 32;
|
||||||
|
|
||||||
private final File testDir = TestUtils.getTestDirectory();
|
private final File testDir = getTestDirectory();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
testDir.mkdirs();
|
assumeTrue(isLinux() || isMac());
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSeedsAreDistinct() {
|
public void testSeedsAreDistinct() {
|
||||||
if (!(OsUtils.isLinux())) {
|
|
||||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Set<Bytes> seeds = new HashSet<>();
|
Set<Bytes> seeds = new HashSet<>();
|
||||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi();
|
UnixSecureRandomSpi engine = new UnixSecureRandomSpi();
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 0; i < 1000; i++) {
|
||||||
byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
|
byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
|
||||||
assertEquals(SEED_BYTES, seed.length);
|
assertEquals(SEED_BYTES, seed.length);
|
||||||
@@ -49,19 +50,15 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEngineSetSeedWritesToRandomDevice() throws Exception {
|
public void testEngineSetSeedWritesToRandomDevice() throws Exception {
|
||||||
if (!(OsUtils.isLinux())) {
|
|
||||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Redirect the engine's output to a file
|
// Redirect the engine's output to a file
|
||||||
File urandom = new File(testDir, "urandom");
|
File urandom = new File(testDir, "urandom");
|
||||||
urandom.delete();
|
if (urandom.exists()) assertTrue(urandom.delete());
|
||||||
assertTrue(urandom.createNewFile());
|
assertTrue(urandom.createNewFile());
|
||||||
assertEquals(0, urandom.length());
|
assertEquals(0, urandom.length());
|
||||||
// Generate a seed
|
// Generate a seed
|
||||||
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
|
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
|
||||||
// Check that the engine writes the seed to the file
|
// Check that the engine writes the seed to the file
|
||||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(RANDOM_DEVICE,
|
UnixSecureRandomSpi engine = new UnixSecureRandomSpi(RANDOM_DEVICE,
|
||||||
urandom);
|
urandom);
|
||||||
engine.engineSetSeed(seed);
|
engine.engineSetSeed(seed);
|
||||||
assertEquals(SEED_BYTES, urandom.length());
|
assertEquals(SEED_BYTES, urandom.length());
|
||||||
@@ -74,15 +71,11 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
|
public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
|
||||||
if (!(OsUtils.isLinux())) {
|
|
||||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Generate some entropy
|
// Generate some entropy
|
||||||
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
|
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
|
||||||
// Write the entropy to a file
|
// Write the entropy to a file
|
||||||
File urandom = new File(testDir, "urandom");
|
File urandom = new File(testDir, "urandom");
|
||||||
urandom.delete();
|
if (urandom.exists()) assertTrue(urandom.delete());
|
||||||
FileOutputStream out = new FileOutputStream(urandom);
|
FileOutputStream out = new FileOutputStream(urandom);
|
||||||
out.write(entropy);
|
out.write(entropy);
|
||||||
out.flush();
|
out.flush();
|
||||||
@@ -90,7 +83,7 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
|||||||
assertTrue(urandom.exists());
|
assertTrue(urandom.exists());
|
||||||
assertEquals(SEED_BYTES, urandom.length());
|
assertEquals(SEED_BYTES, urandom.length());
|
||||||
// Check that the engine reads from the file
|
// Check that the engine reads from the file
|
||||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
|
UnixSecureRandomSpi engine = new UnixSecureRandomSpi(urandom,
|
||||||
RANDOM_DEVICE);
|
RANDOM_DEVICE);
|
||||||
byte[] b = new byte[SEED_BYTES];
|
byte[] b = new byte[SEED_BYTES];
|
||||||
engine.engineNextBytes(b);
|
engine.engineNextBytes(b);
|
||||||
@@ -99,15 +92,11 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
|
public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
|
||||||
if (!(OsUtils.isLinux())) {
|
|
||||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Generate some entropy
|
// Generate some entropy
|
||||||
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
|
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
|
||||||
// Write the entropy to a file
|
// Write the entropy to a file
|
||||||
File urandom = new File(testDir, "urandom");
|
File urandom = new File(testDir, "urandom");
|
||||||
urandom.delete();
|
if (urandom.exists()) assertTrue(urandom.delete());
|
||||||
FileOutputStream out = new FileOutputStream(urandom);
|
FileOutputStream out = new FileOutputStream(urandom);
|
||||||
out.write(entropy);
|
out.write(entropy);
|
||||||
out.flush();
|
out.flush();
|
||||||
@@ -115,7 +104,7 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
|||||||
assertTrue(urandom.exists());
|
assertTrue(urandom.exists());
|
||||||
assertEquals(SEED_BYTES, urandom.length());
|
assertEquals(SEED_BYTES, urandom.length());
|
||||||
// Check that the engine reads from the file
|
// Check that the engine reads from the file
|
||||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
|
UnixSecureRandomSpi engine = new UnixSecureRandomSpi(urandom,
|
||||||
RANDOM_DEVICE);
|
RANDOM_DEVICE);
|
||||||
byte[] b = engine.engineGenerateSeed(SEED_BYTES);
|
byte[] b = engine.engineGenerateSeed(SEED_BYTES);
|
||||||
assertArrayEquals(entropy, b);
|
assertArrayEquals(entropy, b);
|
||||||
@@ -123,6 +112,6 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectory(testDir);
|
deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.data.BdfDictionary;
|
|||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
@@ -43,6 +44,7 @@ import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
|||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||||
@@ -657,4 +659,327 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
c.registerClient(clientId, 123, 234, hook);
|
c.registerClient(clientId, 123, 234, hook);
|
||||||
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsInvisibleIfContactGroupDoesNotExist()
|
||||||
|
throws Exception {
|
||||||
|
expectGetContactGroup(false);
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsInvisibleIfNoRemoteUpdateExists() throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(singletonMap(localUpdateId, localUpdateMeta)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DbException.class)
|
||||||
|
public void testThrowsExceptionIfNoLocalUpdateExists() throws Exception {
|
||||||
|
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(singletonMap(remoteUpdateId, remoteUpdateMeta)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
c.getClientVisibility(txn, contact.getId(), clientId, 123);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsInvisibleIfClientNotSupportedLocally()
|
||||||
|
throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||||
|
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||||
|
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||||
|
// The client is supported remotely but not locally
|
||||||
|
BdfList localUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||||
|
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||||
|
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||||
|
will(returnValue(localUpdateBody));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||||
|
will(returnValue(remoteUpdateBody));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsInvisibleIfClientNotSupportedRemotely()
|
||||||
|
throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||||
|
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||||
|
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||||
|
// The client is supported locally but not remotely
|
||||||
|
BdfList localUpdateBody = BdfList.of(BdfList.of(
|
||||||
|
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||||
|
BdfList remoteUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||||
|
will(returnValue(localUpdateBody));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||||
|
will(returnValue(remoteUpdateBody));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsVisibleIfClientNotActiveRemotely() throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||||
|
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||||
|
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||||
|
// The client is supported locally and remotely but not active
|
||||||
|
BdfList localUpdateBody = BdfList.of(BdfList.of(
|
||||||
|
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||||
|
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||||
|
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||||
|
will(returnValue(localUpdateBody));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||||
|
will(returnValue(remoteUpdateBody));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(VISIBLE, c.getClientVisibility(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsSharedIfClientActiveRemotely() throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||||
|
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||||
|
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||||
|
// The client is supported locally and remotely and active
|
||||||
|
BdfList localUpdateBody = BdfList.of(BdfList.of(
|
||||||
|
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
|
||||||
|
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||||
|
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
|
||||||
|
will(returnValue(localUpdateBody));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||||
|
will(returnValue(remoteUpdateBody));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(SHARED, c.getClientVisibility(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsNegativeIfContactGroupDoesNotExist()
|
||||||
|
throws Exception {
|
||||||
|
expectGetContactGroup(false);
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsNegativeIfNoRemoteUpdateExists() throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(singletonMap(localUpdateId, localUpdateMeta)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsNegativeIfClientNotSupportedRemotely()
|
||||||
|
throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||||
|
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||||
|
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||||
|
// The client is not supported remotely
|
||||||
|
BdfList remoteUpdateBody = BdfList.of(new BdfList(), 1L);
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||||
|
will(returnValue(remoteUpdateBody));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsMinorVersionIfClientNotActiveRemotely()
|
||||||
|
throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||||
|
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||||
|
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||||
|
// The client is supported remotely but not active
|
||||||
|
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||||
|
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||||
|
will(returnValue(remoteUpdateBody));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(234, c.getClientMinorVersion(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsMinorVersionIfClientActiveRemotely()
|
||||||
|
throws Exception {
|
||||||
|
MessageId localUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary localUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||||
|
MessageId remoteUpdateId = new MessageId(getRandomId());
|
||||||
|
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
|
||||||
|
messageMetadata.put(localUpdateId, localUpdateMeta);
|
||||||
|
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
|
||||||
|
// The client is supported remotely and active
|
||||||
|
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
|
||||||
|
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
|
||||||
|
|
||||||
|
expectGetContactGroup(true);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
|
||||||
|
will(returnValue(remoteUpdateBody));
|
||||||
|
}});
|
||||||
|
|
||||||
|
ClientVersioningManagerImpl c = createInstance();
|
||||||
|
assertEquals(234, c.getClientMinorVersion(txn, contact.getId(),
|
||||||
|
clientId, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectGetContactGroup(boolean exists) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
|
will(returnValue(contact));
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
oneOf(db).containsGroup(txn, contactGroup.getId());
|
||||||
|
will(returnValue(exists));
|
||||||
|
}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
package org.briarproject.bramble.lifecycle;
|
package org.briarproject.bramble.lifecycle;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
||||||
import org.briarproject.bramble.util.OsUtils;
|
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.OsUtils.isWindows;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class DesktopLifecycleModule extends LifecycleModule {
|
public class DesktopLifecycleModule extends LifecycleModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
ShutdownManager provideDesktopShutdownManager() {
|
ShutdownManager provideDesktopShutdownManager() {
|
||||||
if (OsUtils.isWindows()) return new WindowsShutdownManagerImpl();
|
if (isWindows()) return new WindowsShutdownManagerImpl();
|
||||||
else return new ShutdownManagerImpl();
|
else return new ShutdownManagerImpl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import com.sun.jna.win32.W32APIFunctionMapper;
|
|||||||
import com.sun.jna.win32.W32APITypeMapper;
|
import com.sun.jna.win32.W32APITypeMapper;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.util.OsUtils;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -29,6 +28,7 @@ import static com.sun.jna.Library.OPTION_FUNCTION_MAPPER;
|
|||||||
import static com.sun.jna.Library.OPTION_TYPE_MAPPER;
|
import static com.sun.jna.Library.OPTION_TYPE_MAPPER;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.OsUtils.isWindows;
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -71,7 +71,7 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
|
|||||||
|
|
||||||
// Locking: lock
|
// Locking: lock
|
||||||
private void initialise() {
|
private void initialise() {
|
||||||
if (OsUtils.isWindows()) {
|
if (isWindows()) {
|
||||||
new EventLoop().start();
|
new EventLoop().start();
|
||||||
} else {
|
} else {
|
||||||
LOG.warning("Windows shutdown manager used on non-Windows OS");
|
LOG.warning("Windows shutdown manager used on non-Windows OS");
|
||||||
@@ -111,7 +111,7 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
|
|||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
// Load user32.dll
|
// Load user32.dll
|
||||||
User32 user32 = (User32) Native.loadLibrary("user32",
|
User32 user32 = Native.loadLibrary("user32",
|
||||||
User32.class, options);
|
User32.class, options);
|
||||||
// Create a callback to handle the WM_QUERYENDSESSION message
|
// Create a callback to handle the WM_QUERYENDSESSION message
|
||||||
WindowProc proc = (hwnd, msg, wp, lp) -> {
|
WindowProc proc = (hwnd, msg, wp, lp) -> {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.security.CodeSource;
|
import java.security.CodeSource;
|
||||||
@@ -19,9 +18,9 @@ import java.util.concurrent.Executor;
|
|||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class LinuxTorPlugin extends TorPlugin {
|
abstract class JavaTorPlugin extends TorPlugin {
|
||||||
|
|
||||||
LinuxTorPlugin(Executor ioExecutor, NetworkManager networkManager,
|
JavaTorPlugin(Executor ioExecutor, NetworkManager networkManager,
|
||||||
LocationUtils locationUtils, SocketFactory torSocketFactory,
|
LocationUtils locationUtils, SocketFactory torSocketFactory,
|
||||||
Clock clock, ResourceProvider resourceProvider,
|
Clock clock, ResourceProvider resourceProvider,
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
@@ -34,17 +33,6 @@ class LinuxTorPlugin extends TorPlugin {
|
|||||||
torDirectory);
|
torDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getProcessId() {
|
|
||||||
try {
|
|
||||||
// Java 9: ProcessHandle.current().pid()
|
|
||||||
return Integer.parseInt(
|
|
||||||
new File("/proc/self").getCanonicalFile().getName());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected long getLastUpdateTime() {
|
protected long getLastUpdateTime() {
|
||||||
CodeSource codeSource =
|
CodeSource codeSource =
|
||||||
@@ -58,5 +46,4 @@ class LinuxTorPlugin extends TorPlugin {
|
|||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
|
import org.briarproject.bramble.api.network.NetworkManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class UnixTorPlugin extends JavaTorPlugin {
|
||||||
|
|
||||||
|
UnixTorPlugin(Executor ioExecutor, NetworkManager networkManager,
|
||||||
|
LocationUtils locationUtils, SocketFactory torSocketFactory,
|
||||||
|
Clock clock, ResourceProvider resourceProvider,
|
||||||
|
CircumventionProvider circumventionProvider,
|
||||||
|
BatteryManager batteryManager, Backoff backoff,
|
||||||
|
DuplexPluginCallback callback, String architecture, int maxLatency,
|
||||||
|
int maxIdleTime, File torDirectory) {
|
||||||
|
super(ioExecutor, networkManager, locationUtils, torSocketFactory,
|
||||||
|
clock, resourceProvider, circumventionProvider, batteryManager,
|
||||||
|
backoff, callback, architecture, maxLatency, maxIdleTime,
|
||||||
|
torDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getProcessId() {
|
||||||
|
return CLibrary.INSTANCE.getpid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface CLibrary extends Library {
|
||||||
|
|
||||||
|
CLibrary INSTANCE = (CLibrary) Native.loadLibrary("c", CLibrary.class);
|
||||||
|
|
||||||
|
int getpid();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,14 +22,15 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class LinuxTorPluginFactory implements DuplexPluginFactory {
|
public class UnixTorPluginFactory implements DuplexPluginFactory {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LinuxTorPluginFactory.class.getName());
|
getLogger(UnixTorPluginFactory.class.getName());
|
||||||
|
|
||||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||||
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
|
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
|
||||||
@@ -49,7 +50,7 @@ public class LinuxTorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final File torDirectory;
|
private final File torDirectory;
|
||||||
|
|
||||||
public LinuxTorPluginFactory(Executor ioExecutor,
|
public UnixTorPluginFactory(Executor ioExecutor,
|
||||||
NetworkManager networkManager, LocationUtils locationUtils,
|
NetworkManager networkManager, LocationUtils locationUtils,
|
||||||
EventBus eventBus, SocketFactory torSocketFactory,
|
EventBus eventBus, SocketFactory torSocketFactory,
|
||||||
BackoffFactory backoffFactory, ResourceProvider resourceProvider,
|
BackoffFactory backoffFactory, ResourceProvider resourceProvider,
|
||||||
@@ -95,7 +96,7 @@ public class LinuxTorPluginFactory implements DuplexPluginFactory {
|
|||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
LinuxTorPlugin plugin = new LinuxTorPlugin(ioExecutor, networkManager,
|
UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, networkManager,
|
||||||
locationUtils, torSocketFactory, clock, resourceProvider,
|
locationUtils, torSocketFactory, clock, resourceProvider,
|
||||||
circumventionProvider, batteryManager, backoff, callback,
|
circumventionProvider, batteryManager, backoff, callback,
|
||||||
architecture, MAX_LATENCY, MAX_IDLE_TIME, torDirectory);
|
architecture, MAX_LATENCY, MAX_IDLE_TIME, torDirectory);
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
package org.briarproject.bramble.system;
|
package org.briarproject.bramble.system;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||||
import org.briarproject.bramble.util.OsUtils;
|
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
||||||
|
import static org.briarproject.bramble.util.OsUtils.isMac;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class DesktopSecureRandomModule {
|
public class DesktopSecureRandomModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
SecureRandomProvider provideSecureRandomProvider() {
|
SecureRandomProvider provideSecureRandomProvider() {
|
||||||
return OsUtils.isLinux() ? new LinuxSecureRandomProvider() : null;
|
if (isLinux() || isMac())
|
||||||
|
return new UnixSecureRandomProvider();
|
||||||
|
// TODO: Create a secure random provider for Windows
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
private final File torDir = getTestDirectory();
|
private final File torDir = getTestDirectory();
|
||||||
private final String bridge;
|
private final String bridge;
|
||||||
|
|
||||||
private LinuxTorPluginFactory factory;
|
private UnixTorPluginFactory factory;
|
||||||
|
|
||||||
public BridgeTest(String bridge) {
|
public BridgeTest(String bridge) {
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
@@ -108,7 +108,7 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
return singletonList(bridge);
|
return singletonList(bridge);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
factory = new LinuxTorPluginFactory(ioExecutor, networkManager,
|
factory = new UnixTorPluginFactory(ioExecutor, networkManager,
|
||||||
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||||
resourceProvider, bridgeProvider, batteryManager, clock,
|
resourceProvider, bridgeProvider, batteryManager, clock,
|
||||||
torDir);
|
torDir);
|
||||||
@@ -124,7 +124,7 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
DuplexPlugin duplexPlugin =
|
DuplexPlugin duplexPlugin =
|
||||||
factory.createPlugin(new TorPluginCallBack());
|
factory.createPlugin(new TorPluginCallBack());
|
||||||
assertNotNull(duplexPlugin);
|
assertNotNull(duplexPlugin);
|
||||||
LinuxTorPlugin plugin = (LinuxTorPlugin) duplexPlugin;
|
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
|
||||||
|
|
||||||
LOG.warning("Testing " + bridge);
|
LOG.warning("Testing " + bridge);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||||
implementation "com.android.support:support-annotations:$supportVersion"
|
implementation "com.android.support:support-annotations:$supportVersion"
|
||||||
|
implementation "com.android.support:exifinterface:$supportVersion"
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||||
implementation "android.arch.lifecycle:extensions:1.1.1"
|
implementation "android.arch.lifecycle:extensions:1.1.1"
|
||||||
|
|
||||||
@@ -115,10 +116,17 @@ dependencies {
|
|||||||
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||||
implementation 'com.google.zxing:core:3.3.3'
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
|
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0'
|
||||||
implementation 'com.vanniktech:emoji-google:0.5.1'
|
implementation 'com.vanniktech:emoji-google:0.5.1'
|
||||||
|
def glideVersion = '4.8.0'
|
||||||
|
implementation("com.github.bumptech.glide:glide:$glideVersion") {
|
||||||
|
exclude group: 'com.android.support'
|
||||||
|
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
|
||||||
|
}
|
||||||
|
implementation 'com.github.chrisbanes:PhotoView:2.1.4' // later versions already use androidx
|
||||||
|
|
||||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
|
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
|
||||||
|
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
||||||
|
|
||||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||||
|
|
||||||
@@ -126,7 +134,7 @@ dependencies {
|
|||||||
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
||||||
testImplementation 'org.robolectric:robolectric:4.0.1'
|
testImplementation 'org.robolectric:robolectric:4.0.1'
|
||||||
testImplementation 'org.robolectric:shadows-support-v4:3.3.2'
|
testImplementation 'org.robolectric:shadows-support-v4:3.3.2'
|
||||||
testImplementation 'org.mockito:mockito-core:2.13.0'
|
testImplementation 'org.mockito:mockito-core:2.19.0'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation "org.jmock:jmock:2.8.2"
|
testImplementation "org.jmock:jmock:2.8.2"
|
||||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||||
|
|||||||
@@ -30,3 +30,6 @@
|
|||||||
|
|
||||||
# Emoji
|
# Emoji
|
||||||
-keep class com.vanniktech.emoji.**
|
-keep class com.vanniktech.emoji.**
|
||||||
|
|
||||||
|
# Glide
|
||||||
|
-dontwarn com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper
|
||||||
|
|||||||
BIN
briar-android/src/androidTestOfficial/assets/animated.gif
Normal file
BIN
briar-android/src/androidTestOfficial/assets/animated.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 B |
BIN
briar-android/src/androidTestOfficial/assets/animated2.gif
Normal file
BIN
briar-android/src/androidTestOfficial/assets/animated2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 317 B |
BIN
briar-android/src/androidTestOfficial/assets/error_high.jpg
Normal file
BIN
briar-android/src/androidTestOfficial/assets/error_high.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
BIN
briar-android/src/androidTestOfficial/assets/error_large.gif
Normal file
BIN
briar-android/src/androidTestOfficial/assets/error_large.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 B |
BIN
briar-android/src/androidTestOfficial/assets/error_wide.jpg
Normal file
BIN
briar-android/src/androidTestOfficial/assets/error_wide.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,279 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class AttachmentControllerIntegrationTest {
|
||||||
|
|
||||||
|
private static final String smallKitten =
|
||||||
|
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg/160px-Kitten_in_Rizal_Park%2C_Manila.jpg";
|
||||||
|
private static final String originalKitten =
|
||||||
|
"https://upload.wikimedia.org/wikipedia/commons/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg";
|
||||||
|
private static final String pngKitten =
|
||||||
|
"https://upload.wikimedia.org/wikipedia/commons/c/c8/Young_cat.png";
|
||||||
|
private static final String uberGif =
|
||||||
|
"https://raw.githubusercontent.com/fuzzdb-project/fuzzdb/master/attack/file-upload/malicious-images/uber.gif";
|
||||||
|
private static final String lottaPixel =
|
||||||
|
"https://raw.githubusercontent.com/fuzzdb-project/fuzzdb/master/attack/file-upload/malicious-images/lottapixel.jpg";
|
||||||
|
private static final String imageIoCrash =
|
||||||
|
"https://www.landaire.net/img/crasher.png";
|
||||||
|
private static final String gimpCrash =
|
||||||
|
"https://gitlab.gnome.org/GNOME/gimp/uploads/75f5b7ed3b09b3f1c13f1f65bffe784f/31153c919d3aa634e8e6cff82219fe7352dd8a37.png";
|
||||||
|
private static final String optiPngAfl =
|
||||||
|
"https://sourceforge.net/p/optipng/bugs/64/attachment/test.gif";
|
||||||
|
private static final String librawError =
|
||||||
|
"https://www.libraw.org/sites/libraw.org/files/P1010671.JPG";
|
||||||
|
|
||||||
|
private final AttachmentDimensions dimensions = new AttachmentDimensions(
|
||||||
|
100, 50, 200, 75, 300
|
||||||
|
);
|
||||||
|
private final MessageId msgId = new MessageId(getRandomId());
|
||||||
|
|
||||||
|
private final AttachmentController controller =
|
||||||
|
new AttachmentController(null, dimensions);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallJpegImage() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getUrlInputStream(smallKitten);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(msgId, item.getMessageId());
|
||||||
|
assertEquals(160, item.getWidth());
|
||||||
|
assertEquals(240, item.getHeight());
|
||||||
|
assertEquals(160, item.getThumbnailWidth());
|
||||||
|
assertEquals(240, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/jpeg", item.getMimeType());
|
||||||
|
assertEquals("jpg", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBigJpegImage() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getUrlInputStream(originalKitten);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(msgId, item.getMessageId());
|
||||||
|
assertEquals(1728, item.getWidth());
|
||||||
|
assertEquals(2592, item.getHeight());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.maxHeight, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/jpeg", item.getMimeType());
|
||||||
|
assertEquals("jpg", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallPngImage() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||||
|
InputStream is = getUrlInputStream(pngKitten);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(msgId, item.getMessageId());
|
||||||
|
assertEquals(737, item.getWidth());
|
||||||
|
assertEquals(510, item.getHeight());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(138, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/png", item.getMimeType());
|
||||||
|
assertEquals("png", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUberGif() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getUrlInputStream(uberGif);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(1, item.getWidth());
|
||||||
|
assertEquals(1, item.getHeight());
|
||||||
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/gif", item.getMimeType());
|
||||||
|
assertEquals("gif", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLottaPixels() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getUrlInputStream(lottaPixel);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(64250, item.getWidth());
|
||||||
|
assertEquals(64250, item.getHeight());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/jpeg", item.getMimeType());
|
||||||
|
assertEquals("jpg", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImageIoCrash() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getUrlInputStream(imageIoCrash);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(1184, item.getWidth());
|
||||||
|
assertEquals(448, item.getHeight());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/png", item.getMimeType());
|
||||||
|
assertEquals("png", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGimpCrash() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getUrlInputStream(gimpCrash);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(1, item.getWidth());
|
||||||
|
assertEquals(1, item.getHeight());
|
||||||
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/gif", item.getMimeType());
|
||||||
|
assertEquals("gif", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOptiPngAfl() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getUrlInputStream(optiPngAfl);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(32, item.getWidth());
|
||||||
|
assertEquals(32, item.getHeight());
|
||||||
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/gif", item.getMimeType());
|
||||||
|
assertEquals("gif", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLibrawError() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getUrlInputStream(librawError);
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertTrue(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallAnimatedGifMaxDimensions() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
|
InputStream is = getAssetInputStream("animated.gif");
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(65535, item.getWidth());
|
||||||
|
assertEquals(65535, item.getHeight());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/gif", item.getMimeType());
|
||||||
|
assertEquals("gif", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallAnimatedGifHugeDimensions() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
|
InputStream is = getAssetInputStream("animated2.gif");
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(10000, item.getWidth());
|
||||||
|
assertEquals(10000, item.getHeight());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/gif", item.getMimeType());
|
||||||
|
assertEquals("gif", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallGifLargeDimensions() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
|
InputStream is = getAssetInputStream("error_large.gif");
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(16384, item.getWidth());
|
||||||
|
assertEquals(16384, item.getHeight());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/gif", item.getMimeType());
|
||||||
|
assertEquals("gif", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHighError() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getAssetInputStream("error_high.jpg");
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(1, item.getWidth());
|
||||||
|
assertEquals(10000, item.getHeight());
|
||||||
|
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.maxHeight, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/jpeg", item.getMimeType());
|
||||||
|
assertEquals("jpg", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWideError() throws Exception {
|
||||||
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
|
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||||
|
Attachment a = new Attachment(is);
|
||||||
|
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
||||||
|
assertEquals(1920, item.getWidth());
|
||||||
|
assertEquals(1, item.getHeight());
|
||||||
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||||
|
assertEquals("image/jpeg", item.getMimeType());
|
||||||
|
assertEquals("jpg", item.getExtension());
|
||||||
|
assertFalse(item.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getUrlInputStream(String url) throws IOException {
|
||||||
|
return new URL(url).openStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getAssetInputStream(String name) throws IOException {
|
||||||
|
AssetManager assets = InstrumentationRegistry.getContext().getAssets();
|
||||||
|
return assets.open(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getRandomBytes(int length) {
|
||||||
|
byte[] b = new byte[length];
|
||||||
|
new Random().nextBytes(b);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getRandomId() {
|
||||||
|
return getRandomBytes(UniqueId.LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
|
||||||
|
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ConversationActivityNotSignedInTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ActivityTestRule<ConversationActivity> testRule =
|
||||||
|
new ActivityTestRule<>(ConversationActivity.class, false, false);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openWithoutSignedIn() {
|
||||||
|
Context targetContext = getInstrumentation().getTargetContext();
|
||||||
|
Intent intent = new Intent(targetContext, ConversationActivity.class);
|
||||||
|
intent.putExtra(CONTACT_ID, 1);
|
||||||
|
testRule.launchActivity(intent);
|
||||||
|
|
||||||
|
onView(withText(R.string.sign_in_button))
|
||||||
|
.perform(waitUntilMatches(isDisplayed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest
|
<manifest
|
||||||
package="org.briarproject.briar"
|
package="org.briarproject.briar"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
|
||||||
@@ -28,7 +30,8 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:logo="@mipmap/ic_launcher_round"
|
android:logo="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/BriarTheme">
|
android:theme="@style/BriarTheme"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
|
android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
|
||||||
@@ -113,6 +116,15 @@
|
|||||||
/>
|
/>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".android.conversation.ImageActivity"
|
||||||
|
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||||
|
android:theme="@style/BriarTheme.ActionBarOverlay">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
||||||
android:label="@string/groups_create_group_title"
|
android:label="@string/groups_create_group_title"
|
||||||
@@ -388,7 +400,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.panic.PanicResponderActivity"
|
android:name="org.briarproject.briar.android.panic.PanicResponderActivity"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/Theme.AppCompat.NoActionBar">
|
android:theme="@style/TranslucentTheme">
|
||||||
<!-- this can never have launchMode singleTask or singleInstance! -->
|
<!-- this can never have launchMode singleTask or singleInstance! -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
|
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
|
||||||
@@ -398,12 +410,12 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.logout.ExitActivity"
|
android:name="org.briarproject.briar.android.logout.ExitActivity"
|
||||||
android:theme="@style/Theme.AppCompat.NoActionBar">
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".android.logout.HideUiActivity"
|
android:name=".android.logout.HideUiActivity"
|
||||||
android:theme="@style/Theme.AppCompat.NoActionBar">
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||||
import org.briarproject.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.EmojiTextInputView;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.android.DozeWatchdog;
|
import org.briarproject.briar.api.android.DozeWatchdog;
|
||||||
import org.briarproject.briar.api.android.LockManager;
|
import org.briarproject.briar.api.android.LockManager;
|
||||||
@@ -168,7 +169,9 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
void inject(NotificationCleanupService notificationCleanupService);
|
void inject(NotificationCleanupService notificationCleanupService);
|
||||||
|
|
||||||
void inject(TextInputView textInputView);
|
void inject(EmojiTextInputView textInputView);
|
||||||
|
|
||||||
|
void inject(BriarModelLoader briarModelLoader);
|
||||||
|
|
||||||
// Eager singleton load
|
// Eager singleton load
|
||||||
void inject(AppModule.EagerSingletons init);
|
void inject(AppModule.EagerSingletons init);
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application.ActivityLifecycleCallbacks;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class BackgroundMonitor implements ActivityLifecycleCallbacks {
|
||||||
|
|
||||||
|
private final AtomicInteger foregroundActivities = new AtomicInteger(0);
|
||||||
|
|
||||||
|
boolean isRunningInBackground() {
|
||||||
|
return foregroundActivities.get() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Activity a, @Nullable Bundle state) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStarted(Activity a) {
|
||||||
|
foregroundActivities.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed(Activity a) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused(Activity a) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStopped(Activity a) {
|
||||||
|
foregroundActivities.decrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivitySaveInstanceState(Activity a,
|
||||||
|
@Nullable Bundle outState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityDestroyed(Activity a) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,4 +16,6 @@ public interface BriarApplication {
|
|||||||
AndroidComponent getApplicationComponent();
|
AndroidComponent getApplicationComponent();
|
||||||
|
|
||||||
SharedPreferences getDefaultSharedPreferences();
|
SharedPreferences getDefaultSharedPreferences();
|
||||||
|
|
||||||
|
boolean isRunningInBackground();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.app.ActivityManager.RunningAppProcessInfo;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@@ -30,6 +32,8 @@ import java.util.logging.Handler;
|
|||||||
import java.util.logging.LogRecord;
|
import java.util.logging.LogRecord;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.logging.Level.FINE;
|
import static java.util.logging.Level.FINE;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static org.acra.ReportField.ANDROID_VERSION;
|
import static org.acra.ReportField.ANDROID_VERSION;
|
||||||
@@ -79,6 +83,7 @@ public class BriarApplicationImpl extends Application
|
|||||||
Logger.getLogger(BriarApplicationImpl.class.getName());
|
Logger.getLogger(BriarApplicationImpl.class.getName());
|
||||||
|
|
||||||
private final CachingLogHandler logHandler = new CachingLogHandler();
|
private final CachingLogHandler logHandler = new CachingLogHandler();
|
||||||
|
private final BackgroundMonitor backgroundMonitor = new BackgroundMonitor();
|
||||||
|
|
||||||
private AndroidComponent applicationComponent;
|
private AndroidComponent applicationComponent;
|
||||||
private volatile SharedPreferences prefs;
|
private volatile SharedPreferences prefs;
|
||||||
@@ -115,6 +120,9 @@ public class BriarApplicationImpl extends Application
|
|||||||
|
|
||||||
applicationComponent = createApplicationComponent();
|
applicationComponent = createApplicationComponent();
|
||||||
EmojiManager.install(new GoogleEmojiProvider());
|
EmojiManager.install(new GoogleEmojiProvider());
|
||||||
|
|
||||||
|
if (SDK_INT < 16)
|
||||||
|
registerActivityLifecycleCallbacks(backgroundMonitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AndroidComponent createApplicationComponent() {
|
protected AndroidComponent createApplicationComponent() {
|
||||||
@@ -173,4 +181,15 @@ public class BriarApplicationImpl extends Application
|
|||||||
public SharedPreferences getDefaultSharedPreferences() {
|
public SharedPreferences getDefaultSharedPreferences() {
|
||||||
return prefs;
|
return prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunningInBackground() {
|
||||||
|
if (SDK_INT >= 16) {
|
||||||
|
RunningAppProcessInfo info = new RunningAppProcessInfo();
|
||||||
|
ActivityManager.getMyMemoryState(info);
|
||||||
|
return (info.importance != IMPORTANCE_FOREGROUND);
|
||||||
|
} else {
|
||||||
|
return backgroundMonitor.isRunningInBackground();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.app.ActivityManager.RunningAppProcessInfo;
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
@@ -35,7 +33,6 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
|
||||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
||||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
@@ -74,6 +71,7 @@ public class BriarService extends Service {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private BroadcastReceiver receiver = null;
|
private BroadcastReceiver receiver = null;
|
||||||
|
private BriarApplication app;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidNotificationManager notificationManager;
|
AndroidNotificationManager notificationManager;
|
||||||
@@ -93,8 +91,8 @@ public class BriarService extends Service {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
BriarApplication application = (BriarApplication) getApplication();
|
app = (BriarApplication) getApplication();
|
||||||
application.getApplicationComponent().inject(this);
|
app.getApplicationComponent().inject(this);
|
||||||
|
|
||||||
LOG.info("Created");
|
LOG.info("Created");
|
||||||
if (created.getAndSet(true)) {
|
if (created.getAndSet(true)) {
|
||||||
@@ -220,8 +218,8 @@ public class BriarService extends Service {
|
|||||||
public void onLowMemory() {
|
public void onLowMemory() {
|
||||||
super.onLowMemory();
|
super.onLowMemory();
|
||||||
LOG.warning("Memory is low");
|
LOG.warning("Memory is low");
|
||||||
// Clear the UI - this is done in onTrimMemory() if SDK_INT >= 16
|
// If we're not in the foreground, clear the UI to save memory
|
||||||
if (SDK_INT < 16) hideUi();
|
if (app.isRunningInBackground()) hideUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -235,20 +233,16 @@ public class BriarService extends Service {
|
|||||||
LOG.info("Trim memory: near middle of LRU list");
|
LOG.info("Trim memory: near middle of LRU list");
|
||||||
} else if (level == TRIM_MEMORY_COMPLETE) {
|
} else if (level == TRIM_MEMORY_COMPLETE) {
|
||||||
LOG.info("Trim memory: near end of LRU list");
|
LOG.info("Trim memory: near end of LRU list");
|
||||||
} else if (SDK_INT >= 16) {
|
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) {
|
||||||
if (level == TRIM_MEMORY_RUNNING_MODERATE) {
|
|
||||||
LOG.info("Trim memory: running moderately low");
|
LOG.info("Trim memory: running moderately low");
|
||||||
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
|
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
|
||||||
LOG.info("Trim memory: running low");
|
LOG.info("Trim memory: running low");
|
||||||
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
|
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||||
LOG.info("Trim memory: running critically low");
|
// This level may be received if SDK_INT < 16, although the
|
||||||
|
// constant isn't declared until API level 16
|
||||||
|
LOG.warning("Trim memory: running critically low");
|
||||||
// If we're not in the foreground, clear the UI to save memory
|
// If we're not in the foreground, clear the UI to save memory
|
||||||
RunningAppProcessInfo info = new RunningAppProcessInfo();
|
if (app.isRunningInBackground()) hideUi();
|
||||||
ActivityManager.getMyMemoryState(info);
|
|
||||||
if (info.importance != IMPORTANCE_FOREGROUND) hideUi();
|
|
||||||
} else if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Trim memory: unknown level " + level);
|
|
||||||
}
|
|
||||||
} else if (LOG.isLoggable(INFO)) {
|
} else if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Trim memory: unknown level " + level);
|
LOG.info("Trim memory: unknown level " + level);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,29 @@ package org.briarproject.briar.android;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
import org.briarproject.briar.android.fragment.ErrorFragment;
|
import org.briarproject.briar.android.fragment.ErrorFragment;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||||
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
|
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
|
||||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
public class StartupFailureActivity extends BaseActivity implements
|
public class StartupFailureActivity extends BaseActivity implements
|
||||||
BaseFragmentListener {
|
BaseFragmentListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle state) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
setContentView(R.layout.activity_fragment_container);
|
setContentView(R.layout.activity_fragment_container);
|
||||||
@@ -38,7 +45,7 @@ public class StartupFailureActivity extends BaseActivity implements
|
|||||||
// cancel notification
|
// cancel notification
|
||||||
if (notificationId > -1) {
|
if (notificationId > -1) {
|
||||||
Object o = getSystemService(NOTIFICATION_SERVICE);
|
Object o = getSystemService(NOTIFICATION_SERVICE);
|
||||||
NotificationManager nm = (NotificationManager) o;
|
NotificationManager nm = (NotificationManager) requireNonNull(o);
|
||||||
nm.cancel(notificationId);
|
nm.cancel(notificationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +73,7 @@ public class StartupFailureActivity extends BaseActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void runOnDbThread(Runnable runnable) {
|
public void runOnDbThread(@NonNull Runnable runnable) {
|
||||||
throw new AssertionError("Deprecated and should not be used");
|
throw new AssertionError("Deprecated and should not be used");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,4 +30,10 @@ public interface TestingConstants {
|
|||||||
long EXPIRY_DATE = IS_DEBUG_BUILD || IS_BETA_BUILD ?
|
long EXPIRY_DATE = IS_DEBUG_BUILD || IS_BETA_BUILD ?
|
||||||
BuildConfig.BuildTimestamp + 90 * 24 * 60 * 60 * 1000L :
|
BuildConfig.BuildTimestamp + 90 * 24 * 60 * 60 * 1000L :
|
||||||
Long.MAX_VALUE;
|
Long.MAX_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flag for enabling image attachments.
|
||||||
|
*/
|
||||||
|
boolean FEATURE_FLAG_IMAGE_ATTACHMENTS = IS_DEBUG_BUILD;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ import org.briarproject.briar.android.blog.ReblogFragment;
|
|||||||
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
||||||
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
||||||
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
||||||
import org.briarproject.briar.android.conversation.AliasDialogFragment;
|
|
||||||
import org.briarproject.briar.android.contact.ContactListFragment;
|
import org.briarproject.briar.android.contact.ContactListFragment;
|
||||||
import org.briarproject.briar.android.contact.ContactModule;
|
import org.briarproject.briar.android.contact.ContactModule;
|
||||||
|
import org.briarproject.briar.android.conversation.AliasDialogFragment;
|
||||||
import org.briarproject.briar.android.conversation.ConversationActivity;
|
import org.briarproject.briar.android.conversation.ConversationActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.ImageActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.ImageFragment;
|
||||||
import org.briarproject.briar.android.forum.CreateForumActivity;
|
import org.briarproject.briar.android.forum.CreateForumActivity;
|
||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||||
@@ -29,7 +31,6 @@ import org.briarproject.briar.android.introduction.IntroductionActivity;
|
|||||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||||
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
|
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
|
||||||
import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment;
|
import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment;
|
||||||
import org.briarproject.briar.android.keyagreement.IntroFragment;
|
|
||||||
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
|
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
|
||||||
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment;
|
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment;
|
||||||
import org.briarproject.briar.android.login.AuthorNameFragment;
|
import org.briarproject.briar.android.login.AuthorNameFragment;
|
||||||
@@ -47,7 +48,6 @@ import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
|||||||
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
||||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
|
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
|
||||||
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
|
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.CreateGroupModule;
|
||||||
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
|
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
|
||||||
import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
|
import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
|
||||||
@@ -68,10 +68,8 @@ import org.briarproject.briar.android.sharing.ForumInvitationActivity;
|
|||||||
import org.briarproject.briar.android.sharing.ForumSharingStatusActivity;
|
import org.briarproject.briar.android.sharing.ForumSharingStatusActivity;
|
||||||
import org.briarproject.briar.android.sharing.ShareBlogActivity;
|
import org.briarproject.briar.android.sharing.ShareBlogActivity;
|
||||||
import org.briarproject.briar.android.sharing.ShareBlogFragment;
|
import org.briarproject.briar.android.sharing.ShareBlogFragment;
|
||||||
import org.briarproject.briar.android.sharing.ShareBlogMessageFragment;
|
|
||||||
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
||||||
import org.briarproject.briar.android.sharing.ShareForumFragment;
|
import org.briarproject.briar.android.sharing.ShareForumFragment;
|
||||||
import org.briarproject.briar.android.sharing.ShareForumMessageFragment;
|
|
||||||
import org.briarproject.briar.android.sharing.SharingModule;
|
import org.briarproject.briar.android.sharing.SharingModule;
|
||||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||||
import org.briarproject.briar.android.test.TestDataActivity;
|
import org.briarproject.briar.android.test.TestDataActivity;
|
||||||
@@ -110,6 +108,8 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(ConversationActivity activity);
|
void inject(ConversationActivity activity);
|
||||||
|
|
||||||
|
void inject(ImageActivity activity);
|
||||||
|
|
||||||
void inject(ForumInvitationActivity activity);
|
void inject(ForumInvitationActivity activity);
|
||||||
|
|
||||||
void inject(BlogInvitationActivity activity);
|
void inject(BlogInvitationActivity activity);
|
||||||
@@ -179,8 +179,6 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(CreateGroupFragment fragment);
|
void inject(CreateGroupFragment fragment);
|
||||||
|
|
||||||
void inject(CreateGroupMessageFragment fragment);
|
|
||||||
|
|
||||||
void inject(GroupListFragment fragment);
|
void inject(GroupListFragment fragment);
|
||||||
|
|
||||||
void inject(GroupInviteFragment fragment);
|
void inject(GroupInviteFragment fragment);
|
||||||
@@ -191,20 +189,14 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(FeedFragment fragment);
|
void inject(FeedFragment fragment);
|
||||||
|
|
||||||
void inject(IntroFragment fragment);
|
|
||||||
|
|
||||||
void inject(KeyAgreementFragment fragment);
|
void inject(KeyAgreementFragment fragment);
|
||||||
|
|
||||||
void inject(ContactChooserFragment fragment);
|
void inject(ContactChooserFragment fragment);
|
||||||
|
|
||||||
void inject(ShareForumFragment fragment);
|
void inject(ShareForumFragment fragment);
|
||||||
|
|
||||||
void inject(ShareForumMessageFragment fragment);
|
|
||||||
|
|
||||||
void inject(ShareBlogFragment fragment);
|
void inject(ShareBlogFragment fragment);
|
||||||
|
|
||||||
void inject(ShareBlogMessageFragment fragment);
|
|
||||||
|
|
||||||
void inject(IntroductionMessageFragment fragment);
|
void inject(IntroductionMessageFragment fragment);
|
||||||
|
|
||||||
void inject(SettingsFragment fragment);
|
void inject(SettingsFragment fragment);
|
||||||
@@ -215,4 +207,6 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(AliasDialogFragment aliasDialogFragment);
|
void inject(AliasDialogFragment aliasDialogFragment);
|
||||||
|
|
||||||
|
void inject(ImageFragment imageFragment);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import android.view.ViewGroup.LayoutParams;
|
|||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
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.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.AndroidComponent;
|
import org.briarproject.briar.android.AndroidComponent;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
@@ -51,6 +53,8 @@ import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOT
|
|||||||
* Warning: Some activities don't extend {@link BaseActivity}.
|
* Warning: Some activities don't extend {@link BaseActivity}.
|
||||||
* E.g. {@link DevReportActivity}
|
* E.g. {@link DevReportActivity}
|
||||||
*/
|
*/
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
public abstract class BaseActivity extends AppCompatActivity
|
public abstract class BaseActivity extends AppCompatActivity
|
||||||
implements DestroyableContext, OnTapFilteredListener {
|
implements DestroyableContext, OnTapFilteredListener {
|
||||||
|
|
||||||
@@ -77,6 +81,17 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle state) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
|
// create the ActivityComponent *before* calling super.onCreate()
|
||||||
|
// because it already attaches fragments which need access
|
||||||
|
// to the component for their own injection
|
||||||
|
AndroidComponent applicationComponent =
|
||||||
|
((BriarApplication) getApplication()).getApplicationComponent();
|
||||||
|
activityComponent = DaggerActivityComponent.builder()
|
||||||
|
.androidComponent(applicationComponent)
|
||||||
|
.activityModule(getActivityModule())
|
||||||
|
.forumModule(getForumModule())
|
||||||
|
.build();
|
||||||
|
injectActivity(activityComponent);
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
// WARNING: When removing this or making it possible to turn it off,
|
// WARNING: When removing this or making it possible to turn it off,
|
||||||
@@ -86,17 +101,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
// unlock screen is shown.
|
// unlock screen is shown.
|
||||||
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
||||||
|
|
||||||
AndroidComponent applicationComponent =
|
|
||||||
((BriarApplication) getApplication()).getApplicationComponent();
|
|
||||||
|
|
||||||
activityComponent = DaggerActivityComponent.builder()
|
|
||||||
.androidComponent(applicationComponent)
|
|
||||||
.activityModule(getActivityModule())
|
|
||||||
.forumModule(getForumModule())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
injectActivity(activityComponent);
|
|
||||||
|
|
||||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||||
alc.onActivityCreate(this);
|
alc.onActivityCreate(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package org.briarproject.briar.android.activity;
|
package org.briarproject.briar.android.activity;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.transition.Slide;
|
|
||||||
import android.transition.Transition;
|
import android.transition.Transition;
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.controller.BriarController;
|
import org.briarproject.briar.android.controller.BriarController;
|
||||||
import org.briarproject.briar.android.controller.DbController;
|
import org.briarproject.briar.android.controller.DbController;
|
||||||
@@ -33,10 +33,12 @@ import static android.os.Build.VERSION.SDK_INT;
|
|||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.excludeSystemUi;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||||
|
|
||||||
@SuppressLint("Registered")
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
public abstract class BriarActivity extends BaseActivity {
|
public abstract class BriarActivity extends BaseActivity {
|
||||||
|
|
||||||
public static final String GROUP_ID = "briar.GROUP_ID";
|
public static final String GROUP_ID = "briar.GROUP_ID";
|
||||||
@@ -60,7 +62,8 @@ public abstract class BriarActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int request, int result, Intent data) {
|
protected void onActivityResult(int request, int result,
|
||||||
|
@Nullable Intent data) {
|
||||||
super.onActivityResult(request, result, data);
|
super.onActivityResult(request, result, data);
|
||||||
if (request == REQUEST_PASSWORD) {
|
if (request == REQUEST_PASSWORD) {
|
||||||
// The result can be RESULT_CANCELED if there's no account
|
// The result can be RESULT_CANCELED if there's no account
|
||||||
@@ -89,7 +92,7 @@ public abstract class BriarActivity extends BaseActivity {
|
|||||||
} else if (lockManager.isLocked() && !isFinishing()) {
|
} else if (lockManager.isLocked() && !isFinishing()) {
|
||||||
// Also check that the activity isn't finishing already.
|
// Also check that the activity isn't finishing already.
|
||||||
// This is possible if we finished in onActivityResult().
|
// This is possible if we finished in onActivityResult().
|
||||||
// Lauching another UnlockActivity would cause a loop.
|
// Launching another UnlockActivity would cause a loop.
|
||||||
Intent i = new Intent(this, UnlockActivity.class);
|
Intent i = new Intent(this, UnlockActivity.class);
|
||||||
startActivityForResult(i, REQUEST_UNLOCK);
|
startActivityForResult(i, REQUEST_UNLOCK);
|
||||||
} else if (SDK_INT >= 23) {
|
} else if (SDK_INT >= 23) {
|
||||||
@@ -111,21 +114,32 @@ public abstract class BriarActivity extends BaseActivity {
|
|||||||
lockManager.onActivityStop();
|
lockManager.onActivityStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSceneTransitionAnimation() {
|
protected boolean signedIn() {
|
||||||
if (SDK_INT < 21) return;
|
return briarController.accountSignedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the transition animations.
|
||||||
|
* @param enterTransition used to move views into initial positions
|
||||||
|
* @param exitTransition used to move views out when starting a <b>new</b> activity.
|
||||||
|
* @param returnTransition used when window is closing, because the activity is finishing.
|
||||||
|
*/
|
||||||
|
@RequiresApi(api = 21)
|
||||||
|
public void setSceneTransitionAnimation(
|
||||||
|
@Nullable Transition enterTransition,
|
||||||
|
@Nullable Transition exitTransition,
|
||||||
|
@Nullable Transition returnTransition) {
|
||||||
// workaround for #1007
|
// workaround for #1007
|
||||||
if (isSamsung7()) {
|
if (isSamsung7()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Transition slide = new Slide(Gravity.RIGHT);
|
if (enterTransition != null) excludeSystemUi(enterTransition);
|
||||||
slide.excludeTarget(android.R.id.statusBarBackground, true);
|
if (exitTransition != null) excludeSystemUi(exitTransition);
|
||||||
slide.excludeTarget(android.R.id.navigationBarBackground, true);
|
if (returnTransition != null) excludeSystemUi(returnTransition);
|
||||||
Window window = getWindow();
|
Window window = getWindow();
|
||||||
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
|
window.setEnterTransition(enterTransition);
|
||||||
window.setExitTransition(slide);
|
window.setExitTransition(exitTransition);
|
||||||
window.setEnterTransition(slide);
|
window.setReturnTransition(returnTransition);
|
||||||
window.setTransitionBackgroundFadeDuration(getResources()
|
|
||||||
.getInteger(android.R.integer.config_longAnimTime));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,5 +14,7 @@ public interface RequestCodes {
|
|||||||
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
|
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
|
||||||
int REQUEST_UNLOCK = 11;
|
int REQUEST_UNLOCK = 11;
|
||||||
int REQUEST_KEYGUARD_UNLOCK = 12;
|
int REQUEST_KEYGUARD_UNLOCK = 12;
|
||||||
|
int REQUEST_ATTACH_IMAGE = 13;
|
||||||
|
int REQUEST_SAVE_ATTACHMENT = 14;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import javax.annotation.Nullable;
|
|||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ abstract class BasePostFragment extends BaseFragment {
|
|||||||
static final String POST_ID = "briar.POST_ID";
|
static final String POST_ID = "briar.POST_ID";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BasePostFragment.class.getName());
|
getLogger(BasePostFragment.class.getName());
|
||||||
|
|
||||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
@@ -52,7 +54,7 @@ abstract class BasePostFragment extends BaseFragment {
|
|||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
// retrieve MessageId of blog post from arguments
|
// retrieve MessageId of blog post from arguments
|
||||||
byte[] p = getArguments().getByteArray(POST_ID);
|
byte[] p = requireNonNull(getArguments()).getByteArray(POST_ID);
|
||||||
if (p == null) throw new IllegalStateException("No post ID in args");
|
if (p == null) throw new IllegalStateException("No post ID in args");
|
||||||
postId = new MessageId(p);
|
postId = new MessageId(p);
|
||||||
|
|
||||||
@@ -68,6 +70,7 @@ abstract class BasePostFragment extends BaseFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthorClick(BlogPostItem post) {
|
public void onAuthorClick(BlogPostItem post) {
|
||||||
|
if (getContext() == null) return;
|
||||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import javax.inject.Inject;
|
|||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
||||||
@@ -69,7 +70,6 @@ public class BlogFragment extends BaseFragment
|
|||||||
private boolean isMyBlog = false, canDeleteBlog = false;
|
private boolean isMyBlog = false, canDeleteBlog = false;
|
||||||
|
|
||||||
static BlogFragment newInstance(GroupId groupId) {
|
static BlogFragment newInstance(GroupId groupId) {
|
||||||
|
|
||||||
BlogFragment f = new BlogFragment();
|
BlogFragment f = new BlogFragment();
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
@@ -79,20 +79,27 @@ public class BlogFragment extends BaseFragment
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
blogController.setBlogSharingListener(this);
|
||||||
|
sharingController.setSharingListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater,
|
public View onCreateView(LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
Bundle args = getArguments();
|
Bundle args = requireNonNull(getArguments());
|
||||||
byte[] b = args.getByteArray(GROUP_ID);
|
byte[] b = args.getByteArray(GROUP_ID);
|
||||||
if (b == null) throw new IllegalStateException("No group ID in args");
|
if (b == null) throw new IllegalStateException("No group ID in args");
|
||||||
groupId = new GroupId(b);
|
groupId = new GroupId(b);
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||||
|
|
||||||
adapter =
|
adapter = new BlogPostAdapter(requireNonNull(getActivity()), this,
|
||||||
new BlogPostAdapter(getActivity(), this, getFragmentManager());
|
getFragmentManager());
|
||||||
list = v.findViewById(R.id.postList);
|
list = v.findViewById(R.id.postList);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
@@ -102,13 +109,6 @@ public class BlogFragment extends BaseFragment
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectFragment(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
blogController.setBlogSharingListener(this);
|
|
||||||
sharingController.setSharingListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
@@ -218,7 +218,10 @@ public class BlogFragment extends BaseFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthorClick(BlogPostItem post) {
|
public void onAuthorClick(BlogPostItem post) {
|
||||||
if (post.getGroupId().equals(groupId)) return; // We're already there
|
if (post.getGroupId().equals(groupId) || getContext() == null) {
|
||||||
|
// We're already there
|
||||||
|
return;
|
||||||
|
}
|
||||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||||
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import javax.inject.Inject;
|
|||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
||||||
|
|
||||||
@@ -64,13 +65,18 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
feedController.setFeedListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater,
|
public View onCreateView(LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
requireNonNull(getActivity()).setTitle(R.string.blogs_button);
|
||||||
getActivity().setTitle(R.string.blogs_button);
|
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||||
|
|
||||||
@@ -88,12 +94,6 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectFragment(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
feedController.setFeedListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.UiResultExceptionHandle
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -42,13 +43,17 @@ public class FeedPostFragment extends BasePostFragment {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectFragment(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater,
|
public View onCreateView(LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
Bundle args = requireNonNull(getArguments());
|
||||||
Bundle args = getArguments();
|
|
||||||
byte[] b = args.getByteArray(GROUP_ID);
|
byte[] b = args.getByteArray(GROUP_ID);
|
||||||
if (b == null) throw new IllegalStateException("No group ID in args");
|
if (b == null) throw new IllegalStateException("No group ID in args");
|
||||||
blogId = new GroupId(b);
|
blogId = new GroupId(b);
|
||||||
@@ -61,11 +66,6 @@ public class FeedPostFragment extends BasePostFragment {
|
|||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectFragment(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ public class ReblogActivity extends BriarActivity implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
setSceneTransitionAnimation();
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -18,7 +19,10 @@ import org.briarproject.briar.android.controller.handler.UiExceptionHandler;
|
|||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -27,18 +31,18 @@ import static android.view.View.FOCUS_DOWN;
|
|||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
||||||
|
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ReblogFragment extends BaseFragment implements TextInputListener {
|
public class ReblogFragment extends BaseFragment implements SendListener {
|
||||||
|
|
||||||
public static final String TAG = ReblogFragment.class.getName();
|
public static final String TAG = ReblogFragment.class.getName();
|
||||||
|
|
||||||
private ViewHolder ui;
|
private ViewHolder ui;
|
||||||
private GroupId blogId;
|
|
||||||
private MessageId postId;
|
|
||||||
private BlogPostItem item;
|
private BlogPostItem item;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -70,24 +74,22 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
|||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
|
||||||
Bundle args = getArguments();
|
Bundle args = requireNonNull(getArguments());
|
||||||
blogId = new GroupId(args.getByteArray(GROUP_ID));
|
GroupId blogId =
|
||||||
postId = new MessageId(args.getByteArray(POST_ID));
|
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
|
||||||
|
MessageId postId =
|
||||||
|
new MessageId(requireNonNull(args.getByteArray(POST_ID)));
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_reblog, container, false);
|
View v = inflater.inflate(R.layout.fragment_reblog, container, false);
|
||||||
ui = new ViewHolder(v);
|
ui = new ViewHolder(v);
|
||||||
ui.post.setTransitionName(postId);
|
ui.post.setTransitionName(postId);
|
||||||
ui.input.setSendButtonEnabled(false);
|
TextSendController sendController =
|
||||||
|
new TextSendController(ui.input, this, true);
|
||||||
|
ui.input.setSendController(sendController);
|
||||||
|
ui.input.setEnabled(false);
|
||||||
|
ui.input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH);
|
||||||
showProgressBar();
|
showProgressBar();
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
// TODO: Load blog post when fragment is created. #631
|
|
||||||
feedController.loadBlogPost(blogId, postId,
|
feedController.loadBlogPost(blogId, postId,
|
||||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
||||||
this) {
|
this) {
|
||||||
@@ -102,6 +104,8 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
|||||||
handleDbException(exception);
|
handleDbException(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindViewHolder() {
|
private void bindViewHolder() {
|
||||||
@@ -112,16 +116,14 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
|||||||
ui.post.bindItem(item);
|
ui.post.bindItem(item);
|
||||||
ui.post.hideReblogButton();
|
ui.post.hideReblogButton();
|
||||||
|
|
||||||
ui.input.setListener(this);
|
ui.input.setEnabled(true);
|
||||||
ui.input.setSendButtonEnabled(true);
|
|
||||||
ui.scrollView.post(() -> ui.scrollView.fullScroll(FOCUS_DOWN));
|
ui.scrollView.post(() -> ui.scrollView.fullScroll(FOCUS_DOWN));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(String text) {
|
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||||
ui.input.hideSoftKeyboard();
|
ui.input.hideSoftKeyboard();
|
||||||
String comment = getComment();
|
feedController.repeatPost(item, text,
|
||||||
feedController.repeatPost(item, comment,
|
|
||||||
new UiExceptionHandler<DbException>(this) {
|
new UiExceptionHandler<DbException>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onExceptionUi(DbException exception) {
|
public void onExceptionUi(DbException exception) {
|
||||||
@@ -131,12 +133,6 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String getComment() {
|
|
||||||
if (ui.input.getText().length() == 0) return null;
|
|
||||||
return ui.input.getText().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showProgressBar() {
|
private void showProgressBar() {
|
||||||
ui.progressBar.setVisibility(VISIBLE);
|
ui.progressBar.setVisibility(VISIBLE);
|
||||||
ui.input.setVisibility(GONE);
|
ui.input.setVisibility(GONE);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
@@ -14,19 +14,22 @@ import org.briarproject.bramble.api.FormatException;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.blog.BlogManager;
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
import org.briarproject.briar.api.blog.BlogPost;
|
import org.briarproject.briar.api.blog.BlogPost;
|
||||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -35,10 +38,13 @@ import static android.view.View.GONE;
|
|||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
|
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
public class WriteBlogPostActivity extends BriarActivity
|
public class WriteBlogPostActivity extends BriarActivity
|
||||||
implements OnEditorActionListener, TextInputListener {
|
implements OnEditorActionListener, SendListener {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(WriteBlogPostActivity.class.getName());
|
Logger.getLogger(WriteBlogPostActivity.class.getName());
|
||||||
@@ -58,9 +64,8 @@ public class WriteBlogPostActivity extends BriarActivity
|
|||||||
@Inject
|
@Inject
|
||||||
volatile BlogManager blogManager;
|
volatile BlogManager blogManager;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle state) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
@@ -71,24 +76,10 @@ public class WriteBlogPostActivity extends BriarActivity
|
|||||||
setContentView(R.layout.activity_write_blog_post);
|
setContentView(R.layout.activity_write_blog_post);
|
||||||
|
|
||||||
input = findViewById(R.id.textInput);
|
input = findViewById(R.id.textInput);
|
||||||
input.setSendButtonEnabled(false);
|
TextSendController sendController =
|
||||||
input.addTextChangedListener(new TextWatcher() {
|
new TextSendController(input, this, false);
|
||||||
@Override
|
input.setSendController(sendController);
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH);
|
||||||
int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before,
|
|
||||||
int count) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
enableOrDisablePublishButton();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
input.setListener(this);
|
|
||||||
|
|
||||||
progressBar = findViewById(R.id.progressBar);
|
progressBar = findViewById(R.id.progressBar);
|
||||||
}
|
}
|
||||||
@@ -127,18 +118,15 @@ public class WriteBlogPostActivity extends BriarActivity
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableOrDisablePublishButton() {
|
|
||||||
input.setSendButtonEnabled(input.getText().length() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(String text) {
|
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||||
|
if (isNullOrEmpty(text)) throw new AssertionError();
|
||||||
|
|
||||||
// hide publish button, show progress bar
|
// hide publish button, show progress bar
|
||||||
input.hideSoftKeyboard();
|
input.hideSoftKeyboard();
|
||||||
input.setVisibility(GONE);
|
input.setVisibility(GONE);
|
||||||
progressBar.setVisibility(VISIBLE);
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
|
||||||
text = StringUtils.truncateUtf8(text, MAX_BLOG_POST_TEXT_LENGTH);
|
|
||||||
storePost(text);
|
storePost(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,13 @@ import javax.inject.Inject;
|
|||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||||
import static android.support.v4.view.ViewCompat.getTransitionName;
|
import static android.support.v4.view.ViewCompat.getTransitionName;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -102,8 +104,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
|
|||||||
public View onCreateView(LayoutInflater inflater,
|
public View onCreateView(LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
requireNonNull(getActivity()).setTitle(R.string.contact_list_button);
|
||||||
getActivity().setTitle(R.string.contact_list_button);
|
|
||||||
|
|
||||||
View contentView = inflater.inflate(R.layout.list, container, false);
|
View contentView = inflater.inflate(R.layout.list, container, false);
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
|
|||||||
ContactId contactId = item.getContact().getId();
|
ContactId contactId = item.getContact().getId();
|
||||||
i.putExtra(CONTACT_ID, contactId.getInt());
|
i.putExtra(CONTACT_ID, contactId.getInt());
|
||||||
|
|
||||||
if (SDK_INT >= 23) {
|
if (SDK_INT >= 23 && !isSamsung7()) {
|
||||||
ContactListItemViewHolder holder =
|
ContactListItemViewHolder holder =
|
||||||
(ContactListItemViewHolder) list
|
(ContactListItemViewHolder) list
|
||||||
.getRecyclerView()
|
.getRecyclerView()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.CONTACTS;
|
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.CONTACTS;
|
||||||
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.getContactsFromIds;
|
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.getContactsFromIds;
|
||||||
@@ -50,10 +51,10 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Bundle args = getArguments();
|
Bundle args = requireNonNull(getArguments());
|
||||||
byte[] b = args.getByteArray(GROUP_ID);
|
byte[] b = args.getByteArray(GROUP_ID);
|
||||||
if (b == null) throw new IllegalStateException("No GroupId");
|
if (b == null) throw new IllegalStateException("No GroupId");
|
||||||
groupId = new GroupId(b);
|
groupId = new GroupId(b);
|
||||||
@@ -72,7 +73,7 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
|
|||||||
list.setEmptyImage(R.drawable.ic_empty_state_contact_list);
|
list.setEmptyImage(R.drawable.ic_empty_state_contact_list);
|
||||||
list.setEmptyText(getString(R.string.no_contacts_selector));
|
list.setEmptyText(getString(R.string.no_contacts_selector));
|
||||||
list.setEmptyAction(getString(R.string.no_contacts_selector_action));
|
list.setEmptyAction(getString(R.string.no_contacts_selector_action));
|
||||||
adapter = getAdapter(getContext(), this);
|
adapter = getAdapter(requireNonNull(getContext()), this);
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
|
|
||||||
// restore selected contacts if available
|
// restore selected contacts if available
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
|||||||
|
|
||||||
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
|
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
|
||||||
|
|
||||||
if (getActivity() == null) return;
|
BriarActivity a = (BriarActivity) requireNonNull(getActivity());
|
||||||
((BriarActivity) getActivity()).getActivityComponent().inject(this);
|
a.getActivityComponent().inject(this);
|
||||||
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
|
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
|
||||||
.get(ConversationViewModel.class);
|
.get(ConversationViewModel.class);
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,6 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_alias_dialog, container,
|
View v = inflater.inflate(R.layout.fragment_alias_dialog, container,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
@@ -69,4 +68,5 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
|||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.BitmapFactory.Options;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.media.ExifInterface;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
|
||||||
|
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
|
||||||
|
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
|
||||||
|
import static android.support.media.ExifInterface.TAG_ORIENTATION;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class AttachmentController {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(AttachmentController.class.getName());
|
||||||
|
private static final int READ_LIMIT = 1024 * 8192;
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
private final ImageHelper imageHelper;
|
||||||
|
private final int defaultSize;
|
||||||
|
private final int minWidth, maxWidth;
|
||||||
|
private final int minHeight, maxHeight;
|
||||||
|
|
||||||
|
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
AttachmentController(MessagingManager messagingManager,
|
||||||
|
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.imageHelper = imageHelper;
|
||||||
|
defaultSize = dimensions.defaultSize;
|
||||||
|
minWidth = dimensions.minWidth;
|
||||||
|
maxWidth = dimensions.maxWidth;
|
||||||
|
minHeight = dimensions.minHeight;
|
||||||
|
maxHeight = dimensions.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentController(MessagingManager messagingManager,
|
||||||
|
AttachmentDimensions dimensions) {
|
||||||
|
this(messagingManager, dimensions, new ImageHelper() {
|
||||||
|
@Override
|
||||||
|
public DecodeResult decodeStream(InputStream is) {
|
||||||
|
Options options = new Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeStream(is, null, options);
|
||||||
|
String mimeType = options.outMimeType;
|
||||||
|
if (mimeType == null) mimeType = "";
|
||||||
|
return new DecodeResult(options.outWidth, options.outHeight,
|
||||||
|
mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getExtensionFromMimeType(String mimeType) {
|
||||||
|
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
|
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(MessageId messageId, List<AttachmentItem> attachments) {
|
||||||
|
attachmentCache.put(messageId, attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
List<AttachmentItem> get(MessageId messageId) {
|
||||||
|
return attachmentCache.get(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
||||||
|
List<AttachmentHeader> headers) throws DbException {
|
||||||
|
long start = now();
|
||||||
|
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||||
|
new ArrayList<>(headers.size());
|
||||||
|
for (AttachmentHeader h : headers) {
|
||||||
|
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
||||||
|
attachments.add(new Pair<>(h, a));
|
||||||
|
}
|
||||||
|
logDuration(LOG, "Loading attachment", start);
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
||||||
|
* <p>
|
||||||
|
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
||||||
|
*/
|
||||||
|
List<AttachmentItem> getAttachmentItems(
|
||||||
|
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
||||||
|
boolean needsSize = attachments.size() == 1;
|
||||||
|
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
||||||
|
for (Pair<AttachmentHeader, Attachment> a : attachments) {
|
||||||
|
AttachmentItem item =
|
||||||
|
getAttachmentItem(a.getFirst(), a.getSecond(), needsSize);
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
||||||
|
* {@link InputStream} which will be closed when this method returns.
|
||||||
|
*/
|
||||||
|
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
||||||
|
boolean needsSize) {
|
||||||
|
MessageId messageId = h.getMessageId();
|
||||||
|
if (!needsSize) {
|
||||||
|
String mimeType = h.getContentType();
|
||||||
|
String extension = imageHelper.getExtensionFromMimeType(mimeType);
|
||||||
|
boolean hasError = false;
|
||||||
|
if (extension == null) {
|
||||||
|
extension = "";
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
return new AttachmentItem(messageId, 0, 0, mimeType, extension, 0,
|
||||||
|
0, hasError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Size size = new Size();
|
||||||
|
InputStream is = new MarkEnforcingInputStream(
|
||||||
|
new BufferedInputStream(a.getStream()));
|
||||||
|
is.mark(READ_LIMIT);
|
||||||
|
try {
|
||||||
|
// use exif to get size
|
||||||
|
if (h.getContentType().equals("image/jpeg")) {
|
||||||
|
size = getSizeFromExif(is);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// use BitmapFactory to get size
|
||||||
|
if (size.error) {
|
||||||
|
is.reset();
|
||||||
|
// need to mark again to re-add read limit
|
||||||
|
is.mark(READ_LIMIT);
|
||||||
|
size = getSizeFromBitmap(is);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
} finally {
|
||||||
|
tryToClose(is, LOG, WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate thumbnail size
|
||||||
|
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
||||||
|
if (!size.error) {
|
||||||
|
thumbnailSize =
|
||||||
|
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||||
|
}
|
||||||
|
// get file extension
|
||||||
|
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||||
|
boolean hasError = extension == null || size.error;
|
||||||
|
if (extension == null) extension = "";
|
||||||
|
return new AttachmentItem(messageId, size.width, size.height,
|
||||||
|
size.mimeType, extension, thumbnailSize.width,
|
||||||
|
thumbnailSize.height, hasError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
||||||
|
*/
|
||||||
|
private Size getSizeFromExif(InputStream is) throws IOException {
|
||||||
|
ExifInterface exif = new ExifInterface(is);
|
||||||
|
// these can return 0 independent of default value
|
||||||
|
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
|
||||||
|
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
|
||||||
|
if (width == 0 || height == 0) return new Size();
|
||||||
|
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
|
||||||
|
if (orientation == ORIENTATION_ROTATE_90 ||
|
||||||
|
orientation == ORIENTATION_ROTATE_270 ||
|
||||||
|
orientation == ORIENTATION_TRANSVERSE ||
|
||||||
|
orientation == ORIENTATION_TRANSPOSE) {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
return new Size(height, width, "image/jpeg");
|
||||||
|
}
|
||||||
|
return new Size(width, height, "image/jpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of any image {@link InputStream}.
|
||||||
|
*/
|
||||||
|
private Size getSizeFromBitmap(InputStream is) {
|
||||||
|
DecodeResult result = imageHelper.decodeStream(is);
|
||||||
|
if (result.width < 1 || result.height < 1) return new Size();
|
||||||
|
return new Size(result.width, result.height, result.mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size getThumbnailSize(int width, int height, String mimeType) {
|
||||||
|
float widthPercentage = maxWidth / (float) width;
|
||||||
|
float heightPercentage = maxHeight / (float) height;
|
||||||
|
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||||
|
if (scaleFactor > 1) scaleFactor = 1f;
|
||||||
|
int thumbnailWidth = (int) (width * scaleFactor);
|
||||||
|
int thumbnailHeight = (int) (height * scaleFactor);
|
||||||
|
if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) {
|
||||||
|
widthPercentage = minWidth / (float) width;
|
||||||
|
heightPercentage = minHeight / (float) height;
|
||||||
|
scaleFactor = Math.max(widthPercentage, heightPercentage);
|
||||||
|
thumbnailWidth = (int) (width * scaleFactor);
|
||||||
|
thumbnailHeight = (int) (height * scaleFactor);
|
||||||
|
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
||||||
|
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
||||||
|
}
|
||||||
|
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Size {
|
||||||
|
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
private final String mimeType;
|
||||||
|
private final boolean error;
|
||||||
|
|
||||||
|
private Size(int width, int height, String mimeType) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size() {
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
this.mimeType = "";
|
||||||
|
this.error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
class AttachmentDimensions {
|
||||||
|
|
||||||
|
final int defaultSize;
|
||||||
|
final int minWidth, maxWidth;
|
||||||
|
final int minHeight, maxHeight;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
AttachmentDimensions(int defaultSize, int minWidth, int maxWidth,
|
||||||
|
int minHeight, int maxHeight) {
|
||||||
|
this.defaultSize = defaultSize;
|
||||||
|
this.minWidth = minWidth;
|
||||||
|
this.maxWidth = maxWidth;
|
||||||
|
this.minHeight = minHeight;
|
||||||
|
this.maxHeight = maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||||
|
int defaultSize =
|
||||||
|
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||||
|
int minWidth = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_min_width);
|
||||||
|
int maxWidth = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_max_width);
|
||||||
|
int minHeight = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_min_height);
|
||||||
|
int maxHeight = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_max_height);
|
||||||
|
return new AttachmentDimensions(defaultSize, minWidth, maxWidth,
|
||||||
|
minHeight, minHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentItem implements Parcelable {
|
||||||
|
|
||||||
|
private final MessageId messageId;
|
||||||
|
private final int width, height;
|
||||||
|
private final String mimeType, extension;
|
||||||
|
private final int thumbnailWidth, thumbnailHeight;
|
||||||
|
private final boolean hasError;
|
||||||
|
private final long instanceId;
|
||||||
|
|
||||||
|
public static final Creator<AttachmentItem> CREATOR =
|
||||||
|
new Creator<AttachmentItem>() {
|
||||||
|
@Override
|
||||||
|
public AttachmentItem createFromParcel(Parcel in) {
|
||||||
|
return new AttachmentItem(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttachmentItem[] newArray(int size) {
|
||||||
|
return new AttachmentItem[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
|
||||||
|
|
||||||
|
AttachmentItem(MessageId messageId, int width, int height, String mimeType,
|
||||||
|
String extension, int thumbnailWidth, int thumbnailHeight,
|
||||||
|
boolean hasError) {
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.extension = extension;
|
||||||
|
this.thumbnailWidth = thumbnailWidth;
|
||||||
|
this.thumbnailHeight = thumbnailHeight;
|
||||||
|
this.hasError = hasError;
|
||||||
|
instanceId = NEXT_INSTANCE_ID.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AttachmentItem(Parcel in) {
|
||||||
|
byte[] messageIdByte = new byte[MessageId.LENGTH];
|
||||||
|
in.readByteArray(messageIdByte);
|
||||||
|
messageId = new MessageId(messageIdByte);
|
||||||
|
width = in.readInt();
|
||||||
|
height = in.readInt();
|
||||||
|
mimeType = in.readString();
|
||||||
|
extension = in.readString();
|
||||||
|
thumbnailWidth = in.readInt();
|
||||||
|
thumbnailHeight = in.readInt();
|
||||||
|
hasError = in.readByte() != 0;
|
||||||
|
instanceId = in.readLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageId getMessageId() {
|
||||||
|
return messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getExtension() {
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getThumbnailWidth() {
|
||||||
|
return thumbnailWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getThumbnailHeight() {
|
||||||
|
return thumbnailHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasError() {
|
||||||
|
return hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTransitionName() {
|
||||||
|
return String.valueOf(instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeByteArray(messageId.getBytes());
|
||||||
|
dest.writeInt(width);
|
||||||
|
dest.writeInt(height);
|
||||||
|
dest.writeString(mimeType);
|
||||||
|
dest.writeString(extension);
|
||||||
|
dest.writeInt(thumbnailWidth);
|
||||||
|
dest.writeInt(thumbnailHeight);
|
||||||
|
dest.writeByte((byte) (hasError ? 1 : 0));
|
||||||
|
dest.writeLong(instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object o) {
|
||||||
|
return o instanceof AttachmentItem &&
|
||||||
|
instanceId == ((AttachmentItem) o).instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.arch.lifecycle.Observer;
|
import android.arch.lifecycle.Observer;
|
||||||
import android.arch.lifecycle.ViewModelProvider;
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
import android.arch.lifecycle.ViewModelProviders;
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.app.ActivityOptionsCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.ActionMenuView;
|
import android.support.v7.widget.ActionMenuView;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.transition.Slide;
|
||||||
|
import android.transition.Transition;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -23,7 +30,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
@@ -39,25 +46,27 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.blog.BlogActivity;
|
import org.briarproject.briar.android.blog.BlogActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
|
||||||
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
|
import org.briarproject.briar.android.view.ImagePreview;
|
||||||
|
import org.briarproject.briar.android.view.TextAttachmentController;
|
||||||
|
import org.briarproject.briar.android.view.TextAttachmentController.AttachImageListener;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||||
@@ -69,8 +78,9 @@ import org.briarproject.briar.api.conversation.ConversationResponse;
|
|||||||
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
||||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||||
@@ -92,8 +102,12 @@ import im.delight.android.identicons.IdenticonDrawable;
|
|||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
||||||
|
|
||||||
|
import static android.arch.lifecycle.Lifecycle.State.STARTED;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||||
import static android.support.v4.view.ViewCompat.setTransitionName;
|
import static android.support.v4.view.ViewCompat.setTransitionName;
|
||||||
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||||
|
import static android.view.Gravity.RIGHT;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.sort;
|
import static java.util.Collections.sort;
|
||||||
@@ -103,8 +117,14 @@ import static java.util.logging.Level.WARNING;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_IMAGE_ATTACHMENTS;
|
||||||
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
||||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||||
@@ -115,15 +135,16 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
|
|||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ConversationActivity extends BriarActivity
|
public class ConversationActivity extends BriarActivity
|
||||||
implements EventListener, ConversationListener, TextInputListener,
|
implements EventListener, ConversationListener, SendListener,
|
||||||
TextCache {
|
TextCache, AttachmentCache, AttachImageListener {
|
||||||
|
|
||||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ConversationActivity.class.getName());
|
Logger.getLogger(ConversationActivity.class.getName());
|
||||||
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
|
||||||
"showOnboardingIntroduction";
|
private static final int TRANSITION_DURATION_MS = 500;
|
||||||
|
private static final int ONBOARDING_DELAY_MS = 250;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidNotificationManager notificationManager;
|
AndroidNotificationManager notificationManager;
|
||||||
@@ -132,18 +153,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Inject
|
@Inject
|
||||||
@CryptoExecutor
|
@CryptoExecutor
|
||||||
Executor cryptoExecutor;
|
Executor cryptoExecutor;
|
||||||
|
@Inject
|
||||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
private ConversationViewModel viewModel;
|
|
||||||
private ConversationVisitor visitor;
|
|
||||||
private ConversationAdapter adapter;
|
|
||||||
private Toolbar toolbar;
|
|
||||||
private CircleImageView toolbarAvatar;
|
|
||||||
private ImageView toolbarStatus;
|
|
||||||
private TextView toolbarTitle;
|
|
||||||
private BriarRecyclerView list;
|
|
||||||
private TextInputView textInputView;
|
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
// Fields that are accessed from background threads must be volatile
|
||||||
@Inject
|
@Inject
|
||||||
@@ -166,21 +177,39 @@ public class ConversationActivity extends BriarActivity
|
|||||||
volatile BlogSharingManager blogSharingManager;
|
volatile BlogSharingManager blogSharingManager;
|
||||||
@Inject
|
@Inject
|
||||||
volatile GroupInvitationManager groupInvitationManager;
|
volatile GroupInvitationManager groupInvitationManager;
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
private volatile ContactId contactId;
|
|
||||||
@Nullable
|
|
||||||
private volatile GroupId messagingGroupId;
|
|
||||||
|
|
||||||
|
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||||
private final Observer<String> contactNameObserver = name -> {
|
private final Observer<String> contactNameObserver = name -> {
|
||||||
requireNonNull(name);
|
requireNonNull(name);
|
||||||
loadMessages();
|
loadMessages();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private AttachmentController attachmentController;
|
||||||
|
private ConversationViewModel viewModel;
|
||||||
|
private ConversationVisitor visitor;
|
||||||
|
private ConversationAdapter adapter;
|
||||||
|
private Toolbar toolbar;
|
||||||
|
private CircleImageView toolbarAvatar;
|
||||||
|
private ImageView toolbarStatus;
|
||||||
|
private TextView toolbarTitle;
|
||||||
|
private BriarRecyclerView list;
|
||||||
|
private LinearLayoutManager layoutManager;
|
||||||
|
private TextInputView textInputView;
|
||||||
|
private TextSendController sendController;
|
||||||
|
@Nullable
|
||||||
|
private Parcelable layoutManagerState;
|
||||||
|
|
||||||
|
private volatile ContactId contactId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle state) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
setSceneTransitionAnimation();
|
if (SDK_INT >= 21) {
|
||||||
|
// Spurious lint warning - using END causes a crash
|
||||||
|
@SuppressLint("RtlHardcoded")
|
||||||
|
Transition slide = new Slide(RIGHT);
|
||||||
|
slide.setDuration(TRANSITION_DURATION_MS);
|
||||||
|
setSceneTransitionAnimation(slide, null, slide);
|
||||||
|
}
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
@@ -190,7 +219,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||||
.get(ConversationViewModel.class);
|
.get(ConversationViewModel.class);
|
||||||
viewModel.setContactId(contactId);
|
attachmentController = viewModel.getAttachmentController();
|
||||||
|
|
||||||
setContentView(R.layout.activity_conversation);
|
setContentView(R.layout.activity_conversation);
|
||||||
|
|
||||||
@@ -213,20 +242,45 @@ public class ConversationActivity extends BriarActivity
|
|||||||
requireNonNull(deleted);
|
requireNonNull(deleted);
|
||||||
if (deleted) finish();
|
if (deleted) finish();
|
||||||
});
|
});
|
||||||
|
viewModel.getAddedPrivateMessage().observe(this,
|
||||||
|
this::onAddedPrivateMessage);
|
||||||
|
|
||||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||||
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
||||||
|
|
||||||
visitor = new ConversationVisitor(this, this,
|
visitor = new ConversationVisitor(this, this, this,
|
||||||
viewModel.getContactDisplayName());
|
viewModel.getContactDisplayName());
|
||||||
adapter = new ConversationAdapter(this, this);
|
adapter = new ConversationAdapter(this, this);
|
||||||
list = findViewById(R.id.conversationView);
|
list = findViewById(R.id.conversationView);
|
||||||
list.setLayoutManager(new LinearLayoutManager(this));
|
layoutManager = new LinearLayoutManager(this);
|
||||||
|
list.setLayoutManager(layoutManager);
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.setEmptyText(getString(R.string.no_private_messages));
|
list.setEmptyText(getString(R.string.no_private_messages));
|
||||||
|
|
||||||
textInputView = findViewById(R.id.text_input_container);
|
textInputView = findViewById(R.id.text_input_container);
|
||||||
textInputView.setListener(this);
|
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
|
||||||
|
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
||||||
|
sendController = new TextAttachmentController(textInputView,
|
||||||
|
imagePreview, this, this);
|
||||||
|
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
||||||
|
if (hasSupport != null && hasSupport) {
|
||||||
|
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||||
|
((TextAttachmentController) sendController)
|
||||||
|
.setImagesSupported();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sendController = new TextSendController(textInputView, this, false);
|
||||||
|
}
|
||||||
|
textInputView.setSendController(sendController);
|
||||||
|
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
|
textInputView.setEnabled(false);
|
||||||
|
textInputView.addOnKeyboardShownListener(this::scrollToBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scrollToBottom() {
|
||||||
|
int items = adapter.getItemCount();
|
||||||
|
if (items > 0) list.scrollToPosition(items - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -235,7 +289,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int request, int result, Intent data) {
|
protected void onActivityResult(int request, int result,
|
||||||
|
@Nullable Intent data) {
|
||||||
super.onActivityResult(request, result, data);
|
super.onActivityResult(request, result, data);
|
||||||
|
|
||||||
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
|
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
|
||||||
@@ -243,6 +298,9 @@ public class ConversationActivity extends BriarActivity
|
|||||||
Snackbar.LENGTH_SHORT);
|
Snackbar.LENGTH_SHORT);
|
||||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||||
snackbar.show();
|
snackbar.show();
|
||||||
|
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
|
||||||
|
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||||
|
((TextAttachmentController) sendController).onImageReceived(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +315,16 @@ public class ConversationActivity extends BriarActivity
|
|||||||
list.startPeriodicUpdate();
|
list.startPeriodicUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
// Trigger loading of contact data, noop if data was loaded already.
|
||||||
|
//
|
||||||
|
// We can only start loading data *after* we are sure
|
||||||
|
// the user has signed in. After sign-in, onCreate() isn't run again.
|
||||||
|
if (signedIn()) viewModel.setContactId(contactId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
@@ -266,16 +334,39 @@ public class ConversationActivity extends BriarActivity
|
|||||||
list.stopPeriodicUpdate();
|
list.stopPeriodicUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
if (layoutManager != null) {
|
||||||
|
layoutManagerState = layoutManager.onSaveInstanceState();
|
||||||
|
outState.putParcelable("layoutManager", layoutManagerState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
|
layoutManagerState = savedInstanceState.getParcelable("layoutManager");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
// Inflate the menu items for use in the action bar
|
// Inflate the menu items for use in the action bar
|
||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
inflater.inflate(R.menu.conversation_actions, menu);
|
inflater.inflate(R.menu.conversation_actions, menu);
|
||||||
|
|
||||||
enableIntroductionActionIfAvailable(
|
// enable introduction action if available
|
||||||
menu.findItem(R.id.action_introduction));
|
observeOnce(viewModel.showIntroductionAction(), this, enable -> {
|
||||||
enableAliasActionIfAvailable(
|
if (enable != null && enable) {
|
||||||
menu.findItem(R.id.action_set_alias));
|
menu.findItem(R.id.action_introduction).setEnabled(true);
|
||||||
|
// show introduction onboarding, if needed
|
||||||
|
observeOnce(viewModel.showIntroductionOnboarding(), this,
|
||||||
|
this::showIntroductionOnboarding);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// enable alias action if available
|
||||||
|
observeOnce(viewModel.getContact(), this, contact ->
|
||||||
|
menu.findItem(R.id.action_set_alias).setEnabled(true));
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
@@ -338,17 +429,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
Long.compare(b.getTimestamp(), a.getTimestamp()));
|
Long.compare(b.getTimestamp(), a.getTimestamp()));
|
||||||
if (!sorted.isEmpty()) {
|
if (!sorted.isEmpty()) {
|
||||||
// If the latest header is a private message, eagerly load
|
// If the latest header is a private message, eagerly load
|
||||||
// its text so we can set the scroll position correctly
|
// its size so we can set the scroll position correctly
|
||||||
ConversationMessageHeader latest = sorted.get(0);
|
ConversationMessageHeader latest = sorted.get(0);
|
||||||
if (latest instanceof PrivateMessageHeader &&
|
if (latest instanceof PrivateMessageHeader) {
|
||||||
((PrivateMessageHeader) latest).hasText()) {
|
eagerlyLoadMessageSize((PrivateMessageHeader) latest);
|
||||||
MessageId id = latest.getId();
|
|
||||||
String text = textCache.get(id);
|
|
||||||
if (text == null) {
|
|
||||||
LOG.info("Eagerly loading text of latest message");
|
|
||||||
text = messagingManager.getMessageText(id);
|
|
||||||
textCache.put(id, text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
displayMessages(revision, sorted);
|
displayMessages(revision, sorted);
|
||||||
@@ -360,17 +444,51 @@ public class ConversationActivity extends BriarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void eagerlyLoadMessageSize(PrivateMessageHeader h)
|
||||||
|
throws DbException {
|
||||||
|
MessageId id = h.getId();
|
||||||
|
// If the message has text, load it
|
||||||
|
if (h.hasText()) {
|
||||||
|
String text = textCache.get(id);
|
||||||
|
if (text == null) {
|
||||||
|
LOG.info("Eagerly loading text for latest message");
|
||||||
|
text = messagingManager.getMessageText(id);
|
||||||
|
textCache.put(id, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the message has a single image, load its size - for multiple
|
||||||
|
// images we use a grid so the size is fixed
|
||||||
|
if (h.getAttachmentHeaders().size() == 1) {
|
||||||
|
List<AttachmentItem> items = attachmentController.get(id);
|
||||||
|
if (items == null) {
|
||||||
|
LOG.info("Eagerly loading image size for latest message");
|
||||||
|
items = attachmentController.getAttachmentItems(
|
||||||
|
attachmentController.getMessageAttachments(
|
||||||
|
h.getAttachmentHeaders()));
|
||||||
|
attachmentController.put(id, items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void displayMessages(int revision,
|
private void displayMessages(int revision,
|
||||||
Collection<ConversationMessageHeader> headers) {
|
Collection<ConversationMessageHeader> headers) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
if (revision == adapter.getRevision()) {
|
if (revision == adapter.getRevision()) {
|
||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
textInputView.setSendButtonEnabled(true);
|
textInputView.setEnabled(true);
|
||||||
|
// start observing onboarding after enabling (only once, because
|
||||||
|
// we only update this when an onboarding should be shown)
|
||||||
|
observeOnce(viewModel.showImageOnboarding(), this,
|
||||||
|
this::showImageOnboarding);
|
||||||
List<ConversationItem> items = createItems(headers);
|
List<ConversationItem> items = createItems(headers);
|
||||||
adapter.addAll(items);
|
adapter.addAll(items);
|
||||||
list.showData();
|
list.showData();
|
||||||
// Scroll to the bottom
|
if (layoutManagerState == null) {
|
||||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
scrollToBottom();
|
||||||
|
} else {
|
||||||
|
// Restore the previous scroll position
|
||||||
|
layoutManager.onRestoreInstanceState(layoutManagerState);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Concurrent update, reloading");
|
LOG.info("Concurrent update, reloading");
|
||||||
loadMessages();
|
loadMessages();
|
||||||
@@ -408,16 +526,51 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void displayMessageText(MessageId m, String text) {
|
private void displayMessageText(MessageId m, String text) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
textCache.put(m, text);
|
textCache.put(m, text);
|
||||||
SparseArray<ConversationMessageItem> messages =
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
adapter.getMessageItems();
|
adapter.getMessageItem(m);
|
||||||
for (int i = 0; i < messages.size(); i++) {
|
if (pair != null) {
|
||||||
ConversationItem item = messages.valueAt(i);
|
boolean scroll = shouldScrollWhenUpdatingMessage();
|
||||||
if (item.getId().equals(m)) {
|
pair.getSecond().setText(text);
|
||||||
item.setText(text);
|
adapter.notifyItemChanged(pair.getFirst());
|
||||||
adapter.notifyItemChanged(messages.keyAt(i));
|
if (scroll) scrollToBottom();
|
||||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a message's text or attachments are loaded, scroll to the bottom
|
||||||
|
// if the conversation is visible and we were previously at the bottom
|
||||||
|
private boolean shouldScrollWhenUpdatingMessage() {
|
||||||
|
return getLifecycle().getCurrentState().isAtLeast(STARTED)
|
||||||
|
&& adapter.isScrolledToBottom(layoutManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMessageAttachments(MessageId messageId,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
try {
|
||||||
|
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||||
|
attachmentController.getMessageAttachments(headers);
|
||||||
|
// TODO move getting the items off to IoExecutor, if size == 1
|
||||||
|
List<AttachmentItem> items =
|
||||||
|
attachmentController.getAttachmentItems(attachments);
|
||||||
|
displayMessageAttachments(messageId, items);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayMessageAttachments(MessageId m,
|
||||||
|
List<AttachmentItem> items) {
|
||||||
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
|
attachmentController.put(m, items);
|
||||||
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
|
adapter.getMessageItem(m);
|
||||||
|
if (pair != null) {
|
||||||
|
boolean scroll = shouldScrollWhenUpdatingMessage();
|
||||||
|
pair.getSecond().setAttachments(items);
|
||||||
|
adapter.notifyItemChanged(pair.getFirst());
|
||||||
|
if (scroll) scrollToBottom();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -468,8 +621,11 @@ public class ConversationActivity extends BriarActivity
|
|||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
adapter.add(item);
|
adapter.add(item);
|
||||||
// Scroll to the bottom
|
// When adding a new message, scroll to the bottom if the
|
||||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
// conversation is visible, even if we're not currently at
|
||||||
|
// the bottom
|
||||||
|
if (getLifecycle().getCurrentState().isAtLeast(STARTED))
|
||||||
|
scrollToBottom();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,14 +661,18 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(String text) {
|
public void onAttachImage(Intent intent) {
|
||||||
if (text.isEmpty()) return;
|
startActivityForResult(intent, REQUEST_ATTACH_IMAGE);
|
||||||
text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
||||||
|
if (isNullOrEmpty(text) && imageUris.isEmpty())
|
||||||
|
throw new AssertionError();
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
||||||
if (messagingGroupId == null) loadGroupId(text, timestamp);
|
viewModel.sendMessage(text, imageUris, timestamp);
|
||||||
else createMessage(text, timestamp);
|
textInputView.clearText();
|
||||||
textInputView.setText("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getMinTimestampForNewMessage() {
|
private long getMinTimestampForNewMessage() {
|
||||||
@@ -521,48 +681,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
return item == null ? 0 : item.getTime() + 1;
|
return item == null ? 0 : item.getTime() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadGroupId(String text, long timestamp) {
|
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
||||||
runOnDbThread(() -> {
|
if (h == null) return;
|
||||||
try {
|
|
||||||
messagingGroupId =
|
|
||||||
messagingManager.getConversationId(contactId);
|
|
||||||
createMessage(text, timestamp);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createMessage(String text, long timestamp) {
|
|
||||||
cryptoExecutor.execute(() -> {
|
|
||||||
try {
|
|
||||||
//noinspection ConstantConditions init in loadGroupId()
|
|
||||||
storeMessage(privateMessageFactory.createPrivateMessage(
|
|
||||||
messagingGroupId, timestamp, text, emptyList()), text);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeMessage(PrivateMessage m, String text) {
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
messagingManager.addLocalMessage(m);
|
|
||||||
logDuration(LOG, "Storing message", start);
|
|
||||||
Message message = m.getMessage();
|
|
||||||
PrivateMessageHeader h = new PrivateMessageHeader(
|
|
||||||
message.getId(), message.getGroupId(),
|
|
||||||
message.getTimestamp(), true, false, false, false,
|
|
||||||
true, emptyList());
|
|
||||||
textCache.put(message.getId(), text);
|
|
||||||
addConversationItem(h.accept(visitor));
|
addConversationItem(h.accept(visitor));
|
||||||
} catch (DbException e) {
|
viewModel.onAddedPrivateMessageSeen();
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void askToRemoveContact() {
|
private void askToRemoveContact() {
|
||||||
@@ -599,41 +721,48 @@ public class ConversationActivity extends BriarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableIntroductionActionIfAvailable(MenuItem item) {
|
private void showImageOnboarding(@Nullable Boolean show) {
|
||||||
runOnDbThread(() -> {
|
if (show == null || !show) return;
|
||||||
try {
|
if (SDK_INT >= 21) {
|
||||||
if (contactManager.getActiveContacts().size() > 1) {
|
// show onboarding only after the enter transition has ended
|
||||||
enableIntroductionAction(item);
|
// otherwise the tap target animation won't play
|
||||||
Settings settings =
|
textInputView.postDelayed(this::showImageOnboarding,
|
||||||
settingsManager.getSettings(SETTINGS_NAMESPACE);
|
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS);
|
||||||
if (settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION,
|
} else {
|
||||||
true)) {
|
showImageOnboarding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showImageOnboarding() {
|
||||||
|
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
||||||
|
((TextAttachmentController) sendController)
|
||||||
|
.showImageOnboarding(this, () ->
|
||||||
|
viewModel.onImageOnboardingSeen());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showIntroductionOnboarding(@Nullable Boolean show) {
|
||||||
|
if (show == null || !show) return;
|
||||||
|
if (SDK_INT >= 21) {
|
||||||
|
// show onboarding only after the enter transition has ended
|
||||||
|
// otherwise the tap target animation won't play
|
||||||
|
textInputView.postDelayed(this::showIntroductionOnboarding,
|
||||||
|
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS);
|
||||||
|
} else {
|
||||||
showIntroductionOnboarding();
|
showIntroductionOnboarding();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableAliasActionIfAvailable(MenuItem item) {
|
|
||||||
observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableIntroductionAction(MenuItem item) {
|
|
||||||
runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showIntroductionOnboarding() {
|
private void showIntroductionOnboarding() {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
|
||||||
// find view of overflow icon
|
// find view of overflow icon
|
||||||
View target = null;
|
View target = null;
|
||||||
for (int i = 0; i < toolbar.getChildCount(); i++) {
|
for (int i = 0; i < toolbar.getChildCount(); i++) {
|
||||||
if (toolbar.getChildAt(i) instanceof ActionMenuView) {
|
if (toolbar.getChildAt(i) instanceof ActionMenuView) {
|
||||||
ActionMenuView menu =
|
ActionMenuView menu = (ActionMenuView) toolbar.getChildAt(i);
|
||||||
(ActionMenuView) toolbar.getChildAt(i);
|
// The overflow icon should be the last child of the menu
|
||||||
target = menu.getChildAt(menu.getChildCount() - 1);
|
target = menu.getChildAt(menu.getChildCount() - 1);
|
||||||
|
// If the menu hasn't been populated yet, use the menu itself
|
||||||
|
// as the target
|
||||||
|
if (target == null) target = menu;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -644,7 +773,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
PromptStateChangeListener listener = (prompt, state) -> {
|
PromptStateChangeListener listener = (prompt, state) -> {
|
||||||
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
|
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
|
||||||
introductionOnboardingSeen();
|
viewModel.onIntroductionOnboardingSeen();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
|
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
|
||||||
@@ -652,21 +781,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
.setPrimaryText(R.string.introduction_onboarding_title)
|
.setPrimaryText(R.string.introduction_onboarding_title)
|
||||||
.setSecondaryText(R.string.introduction_onboarding_text)
|
.setSecondaryText(R.string.introduction_onboarding_text)
|
||||||
.setIcon(R.drawable.ic_more_vert_accent)
|
.setIcon(R.drawable.ic_more_vert_accent)
|
||||||
|
.setBackgroundColour(
|
||||||
|
ContextCompat.getColor(this, R.color.briar_primary))
|
||||||
.setPromptStateChangeListener(listener)
|
.setPromptStateChangeListener(listener)
|
||||||
.show();
|
.show();
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void introductionOnboardingSeen() {
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
Settings settings = new Settings();
|
|
||||||
settings.putBoolean(SHOW_ONBOARDING_INTRODUCTION, false);
|
|
||||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -750,6 +868,30 @@ public class ConversationActivity extends BriarActivity
|
|||||||
startActivity(i);
|
startActivity(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachmentClicked(View view,
|
||||||
|
ConversationMessageItem messageItem, AttachmentItem item) {
|
||||||
|
String name;
|
||||||
|
if (messageItem.isIncoming()) {
|
||||||
|
// must be available when items are being displayed
|
||||||
|
name = viewModel.getContactDisplayName().getValue();
|
||||||
|
} else {
|
||||||
|
name = getString(R.string.you);
|
||||||
|
}
|
||||||
|
ArrayList<AttachmentItem> attachments =
|
||||||
|
new ArrayList<>(messageItem.getAttachments());
|
||||||
|
Intent i = new Intent(this, ImageActivity.class);
|
||||||
|
i.putParcelableArrayListExtra(ATTACHMENTS, attachments);
|
||||||
|
i.putExtra(ATTACHMENT_POSITION, attachments.indexOf(item));
|
||||||
|
i.putExtra(NAME, name);
|
||||||
|
i.putExtra(DATE, messageItem.getTime());
|
||||||
|
// restoring list position should not trigger android bug #224270
|
||||||
|
String transitionName = item.getTransitionName();
|
||||||
|
ActivityOptionsCompat options =
|
||||||
|
makeSceneTransitionAnimation(this, view, transitionName);
|
||||||
|
ActivityCompat.startActivity(this, i, options.toBundle());
|
||||||
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void respondToIntroductionRequest(SessionId sessionId,
|
private void respondToIntroductionRequest(SessionId sessionId,
|
||||||
boolean accept, long time) throws DbException {
|
boolean accept, long time) throws DbException {
|
||||||
@@ -782,4 +924,16 @@ public class ConversationActivity extends BriarActivity
|
|||||||
if (text == null) loadMessageText(m);
|
if (text == null) loadMessageText(m);
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
|
List<AttachmentItem> attachments = attachmentController.get(m);
|
||||||
|
if (attachments == null) {
|
||||||
|
loadMessageAttachments(m, headers);
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ package org.briarproject.briar.android.conversation;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView.RecycledViewPool;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.util.BriarAdapter;
|
import org.briarproject.briar.android.util.BriarAdapter;
|
||||||
|
|
||||||
@@ -18,11 +22,17 @@ class ConversationAdapter
|
|||||||
extends BriarAdapter<ConversationItem, ConversationItemViewHolder> {
|
extends BriarAdapter<ConversationItem, ConversationItemViewHolder> {
|
||||||
|
|
||||||
private ConversationListener listener;
|
private ConversationListener listener;
|
||||||
|
private final RecycledViewPool imageViewPool;
|
||||||
|
private final ImageItemDecoration imageItemDecoration;
|
||||||
|
|
||||||
ConversationAdapter(Context ctx,
|
ConversationAdapter(Context ctx,
|
||||||
ConversationListener conversationListener) {
|
ConversationListener conversationListener) {
|
||||||
super(ctx, ConversationItem.class);
|
super(ctx, ConversationItem.class);
|
||||||
listener = conversationListener;
|
listener = conversationListener;
|
||||||
|
// This shares the same pool for view recycling between all image lists
|
||||||
|
imageViewPool = new RecycledViewPool();
|
||||||
|
// Share the item decoration as well
|
||||||
|
imageItemDecoration = new ImageItemDecoration(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
@@ -39,15 +49,17 @@ class ConversationAdapter
|
|||||||
type, viewGroup, false);
|
type, viewGroup, false);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case R.layout.list_item_conversation_msg_in:
|
case R.layout.list_item_conversation_msg_in:
|
||||||
return new ConversationMessageViewHolder(v, true);
|
return new ConversationMessageViewHolder(v, listener, true,
|
||||||
|
imageViewPool, imageItemDecoration);
|
||||||
case R.layout.list_item_conversation_msg_out:
|
case R.layout.list_item_conversation_msg_out:
|
||||||
return new ConversationMessageViewHolder(v, false);
|
return new ConversationMessageViewHolder(v, listener, false,
|
||||||
|
imageViewPool, imageItemDecoration);
|
||||||
case R.layout.list_item_conversation_notice_in:
|
case R.layout.list_item_conversation_notice_in:
|
||||||
return new ConversationNoticeViewHolder(v, true);
|
return new ConversationNoticeViewHolder(v, listener, true);
|
||||||
case R.layout.list_item_conversation_notice_out:
|
case R.layout.list_item_conversation_notice_out:
|
||||||
return new ConversationNoticeViewHolder(v, false);
|
return new ConversationNoticeViewHolder(v, listener, false);
|
||||||
case R.layout.list_item_conversation_request:
|
case R.layout.list_item_conversation_request:
|
||||||
return new ConversationRequestViewHolder(v, true);
|
return new ConversationRequestViewHolder(v, listener, true);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown ConversationItem");
|
throw new IllegalArgumentException("Unknown ConversationItem");
|
||||||
}
|
}
|
||||||
@@ -56,7 +68,7 @@ class ConversationAdapter
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
|
public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
|
||||||
ConversationItem item = items.get(position);
|
ConversationItem item = items.get(position);
|
||||||
ui.bind(item, listener);
|
ui.bind(item);
|
||||||
listener.onItemVisible(item);
|
listener.onItemVisible(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,16 +110,20 @@ class ConversationAdapter
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
SparseArray<ConversationMessageItem> getMessageItems() {
|
@Nullable
|
||||||
SparseArray<ConversationMessageItem> messages = new SparseArray<>();
|
Pair<Integer, ConversationMessageItem> getMessageItem(MessageId messageId) {
|
||||||
|
|
||||||
for (int i = 0; i < items.size(); i++) {
|
for (int i = 0; i < items.size(); i++) {
|
||||||
ConversationItem item = items.get(i);
|
ConversationItem item = items.get(i);
|
||||||
if (item instanceof ConversationMessageItem) {
|
if (item instanceof ConversationMessageItem &&
|
||||||
messages.put(i, (ConversationMessageItem) item);
|
item.getId().equals(messageId)) {
|
||||||
|
return new Pair<>(i, (ConversationMessageItem) item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return messages;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isScrolledToBottom(LinearLayoutManager layoutManager) {
|
||||||
|
return layoutManager.findLastVisibleItemPosition() == items.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package org.briarproject.briar.android.conversation;
|
|||||||
import android.support.annotation.CallSuper;
|
import android.support.annotation.CallSuper;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
import android.support.constraint.ConstraintLayout;
|
||||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -18,14 +18,17 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class ConversationItemViewHolder extends ViewHolder {
|
abstract class ConversationItemViewHolder extends ViewHolder {
|
||||||
|
|
||||||
protected final ViewGroup layout;
|
protected final ConversationListener listener;
|
||||||
|
protected final ConstraintLayout layout;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final OutItemViewHolder outViewHolder;
|
private final OutItemViewHolder outViewHolder;
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
private final TextView time;
|
protected final TextView time;
|
||||||
|
|
||||||
ConversationItemViewHolder(View v, boolean isIncoming) {
|
ConversationItemViewHolder(View v, ConversationListener listener,
|
||||||
|
boolean isIncoming) {
|
||||||
super(v);
|
super(v);
|
||||||
|
this.listener = listener;
|
||||||
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
|
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
|
||||||
layout = v.findViewById(R.id.layout);
|
layout = v.findViewById(R.id.layout);
|
||||||
text = v.findViewById(R.id.text);
|
text = v.findViewById(R.id.text);
|
||||||
@@ -33,7 +36,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
void bind(ConversationItem item, ConversationListener listener) {
|
void bind(ConversationItem item) {
|
||||||
if (item.getText() != null) {
|
if (item.getText() != null) {
|
||||||
text.setText(trim(item.getText()));
|
text.setText(trim(item.getText()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
@@ -14,4 +15,7 @@ interface ConversationListener {
|
|||||||
|
|
||||||
void openRequestedShareable(ConversationRequestItem item);
|
void openRequestedShareable(ConversationRequestItem item);
|
||||||
|
|
||||||
|
void onAttachmentClicked(View view, ConversationMessageItem messageItem,
|
||||||
|
AttachmentItem attachmentItem);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.conversation;
|
|||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -14,15 +13,20 @@ import javax.annotation.concurrent.NotThreadSafe;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ConversationMessageItem extends ConversationItem {
|
class ConversationMessageItem extends ConversationItem {
|
||||||
|
|
||||||
private final List<AttachmentHeader> attachments;
|
private List<AttachmentItem> attachments;
|
||||||
|
|
||||||
ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h) {
|
ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h,
|
||||||
|
List<AttachmentItem> attachments) {
|
||||||
super(layoutRes, h);
|
super(layoutRes, h);
|
||||||
this.attachments = h.getAttachmentHeaders();
|
this.attachments = attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AttachmentHeader> getAttachments() {
|
List<AttachmentItem> getAttachments() {
|
||||||
return attachments;
|
return attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAttachments(List<AttachmentItem> attachments) {
|
||||||
|
this.attachments = attachments;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,113 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
import android.support.constraint.ConstraintSet;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.RecyclerView.RecycledViewPool;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
|
||||||
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
||||||
|
|
||||||
// image support will be added here (#1242)
|
private final ImageAdapter adapter;
|
||||||
|
private final ViewGroup statusLayout;
|
||||||
|
private final int timeColor, timeColorBubble;
|
||||||
|
private final ConstraintSet textConstraints = new ConstraintSet();
|
||||||
|
private final ConstraintSet imageConstraints = new ConstraintSet();
|
||||||
|
private final ConstraintSet imageTextConstraints = new ConstraintSet();
|
||||||
|
|
||||||
ConversationMessageViewHolder(View v, boolean isIncoming) {
|
ConversationMessageViewHolder(View v, ConversationListener listener,
|
||||||
super(v, isIncoming);
|
boolean isIncoming, RecycledViewPool imageViewPool,
|
||||||
|
ImageItemDecoration imageItemDecoration) {
|
||||||
|
super(v, listener, isIncoming);
|
||||||
|
statusLayout = v.findViewById(R.id.statusLayout);
|
||||||
|
|
||||||
|
// image list
|
||||||
|
RecyclerView list = v.findViewById(R.id.imageList);
|
||||||
|
list.setRecycledViewPool(imageViewPool);
|
||||||
|
adapter = new ImageAdapter(v.getContext(), listener);
|
||||||
|
list.setAdapter(adapter);
|
||||||
|
list.addItemDecoration(imageItemDecoration);
|
||||||
|
|
||||||
|
// remember original status text color
|
||||||
|
timeColor = time.getCurrentTextColor();
|
||||||
|
timeColorBubble = getColor(v.getContext(), R.color.briar_white);
|
||||||
|
|
||||||
|
// clone constraint sets from layout files
|
||||||
|
textConstraints
|
||||||
|
.clone(v.getContext(), R.layout.list_item_conversation_msg_in);
|
||||||
|
imageConstraints.clone(v.getContext(),
|
||||||
|
R.layout.list_item_conversation_msg_image);
|
||||||
|
imageTextConstraints.clone(v.getContext(),
|
||||||
|
R.layout.list_item_conversation_msg_image_text);
|
||||||
|
|
||||||
|
// in/out are different layouts, so we need to do this only once
|
||||||
|
textConstraints
|
||||||
|
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
|
||||||
|
imageConstraints
|
||||||
|
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
|
||||||
|
imageTextConstraints
|
||||||
|
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void bind(ConversationItem conversationItem) {
|
||||||
|
super.bind(conversationItem);
|
||||||
|
ConversationMessageItem item =
|
||||||
|
(ConversationMessageItem) conversationItem;
|
||||||
|
if (item.getAttachments().isEmpty()) {
|
||||||
|
bindTextItem();
|
||||||
|
} else {
|
||||||
|
bindImageItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindTextItem() {
|
||||||
|
resetStatusLayoutForText();
|
||||||
|
textConstraints.applyTo(layout);
|
||||||
|
adapter.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindImageItem(ConversationMessageItem item) {
|
||||||
|
ConstraintSet constraintSet;
|
||||||
|
if (item.getText() == null) {
|
||||||
|
statusLayout.setBackgroundResource(R.drawable.msg_status_bubble);
|
||||||
|
time.setTextColor(timeColorBubble);
|
||||||
|
constraintSet = imageConstraints;
|
||||||
|
} else {
|
||||||
|
resetStatusLayoutForText();
|
||||||
|
constraintSet = imageTextConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.getAttachments().size() == 1) {
|
||||||
|
// apply image size constraints for a single image
|
||||||
|
AttachmentItem attachment = item.getAttachments().get(0);
|
||||||
|
int width = attachment.getThumbnailWidth();
|
||||||
|
int height = attachment.getThumbnailHeight();
|
||||||
|
constraintSet.constrainWidth(R.id.imageList, width);
|
||||||
|
constraintSet.constrainHeight(R.id.imageList, height);
|
||||||
|
} else {
|
||||||
|
// bubble adapts to size of image list
|
||||||
|
constraintSet.constrainWidth(R.id.imageList, WRAP_CONTENT);
|
||||||
|
constraintSet.constrainHeight(R.id.imageList, WRAP_CONTENT);
|
||||||
|
}
|
||||||
|
constraintSet.applyTo(layout);
|
||||||
|
adapter.setConversationItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetStatusLayoutForText() {
|
||||||
|
statusLayout.setBackgroundResource(0);
|
||||||
|
// also reset padding (the background drawable defines some)
|
||||||
|
statusLayout.setPadding(0, 0, 0, 0);
|
||||||
|
time.setTextColor(timeColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import org.briarproject.briar.android.activity.ActivityScope;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.BriarDataFetcherFactory;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class ConversationModule {
|
||||||
|
|
||||||
|
@ActivityScope
|
||||||
|
@Provides
|
||||||
|
BriarDataFetcherFactory provideBriarDataFetcherFactory(
|
||||||
|
BriarDataFetcherFactory dataFetcherFactory) {
|
||||||
|
return dataFetcherFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,16 +19,17 @@ class ConversationNoticeViewHolder extends ConversationItemViewHolder {
|
|||||||
|
|
||||||
private final TextView msgText;
|
private final TextView msgText;
|
||||||
|
|
||||||
ConversationNoticeViewHolder(View v, boolean isIncoming) {
|
ConversationNoticeViewHolder(View v, ConversationListener listener,
|
||||||
super(v, isIncoming);
|
boolean isIncoming) {
|
||||||
|
super(v, listener, isIncoming);
|
||||||
msgText = v.findViewById(R.id.msgText);
|
msgText = v.findViewById(R.id.msgText);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
@CallSuper
|
||||||
void bind(ConversationItem item, ConversationListener listener) {
|
void bind(ConversationItem item) {
|
||||||
ConversationNoticeItem notice = (ConversationNoticeItem) item;
|
ConversationNoticeItem notice = (ConversationNoticeItem) item;
|
||||||
super.bind(notice, listener);
|
super.bind(notice);
|
||||||
|
|
||||||
String text = notice.getMsgText();
|
String text = notice.getMsgText();
|
||||||
if (isNullOrEmpty(text)) {
|
if (isNullOrEmpty(text)) {
|
||||||
|
|||||||
@@ -17,16 +17,17 @@ class ConversationRequestViewHolder extends ConversationNoticeViewHolder {
|
|||||||
private final Button acceptButton;
|
private final Button acceptButton;
|
||||||
private final Button declineButton;
|
private final Button declineButton;
|
||||||
|
|
||||||
ConversationRequestViewHolder(View v, boolean isIncoming) {
|
ConversationRequestViewHolder(View v, ConversationListener listener,
|
||||||
super(v, isIncoming);
|
boolean isIncoming) {
|
||||||
|
super(v, listener, isIncoming);
|
||||||
acceptButton = v.findViewById(R.id.acceptButton);
|
acceptButton = v.findViewById(R.id.acceptButton);
|
||||||
declineButton = v.findViewById(R.id.declineButton);
|
declineButton = v.findViewById(R.id.declineButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void bind(ConversationItem item, ConversationListener listener) {
|
void bind(ConversationItem item) {
|
||||||
ConversationRequestItem request = (ConversationRequestItem) item;
|
ConversationRequestItem request = (ConversationRequestItem) item;
|
||||||
super.bind(request, listener);
|
super.bind(request);
|
||||||
|
|
||||||
if (request.wasAnswered() && request.canBeOpened()) {
|
if (request.wasAnswered() && request.canBeOpened()) {
|
||||||
acceptButton.setVisibility(VISIBLE);
|
acceptButton.setVisibility(VISIBLE);
|
||||||
|
|||||||
@@ -5,18 +5,40 @@ import android.arch.lifecycle.AndroidViewModel;
|
|||||||
import android.arch.lifecycle.LiveData;
|
import android.arch.lifecycle.LiveData;
|
||||||
import android.arch.lifecycle.MutableLiveData;
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
import android.arch.lifecycle.Transformations;
|
import android.arch.lifecycle.Transformations;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
|
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||||
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -25,19 +47,35 @@ import javax.inject.Inject;
|
|||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
import static org.briarproject.briar.android.conversation.AttachmentDimensions.getAttachmentDimensions;
|
||||||
|
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ConversationViewModel extends AndroidViewModel {
|
public class ConversationViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
private static Logger LOG =
|
private static Logger LOG =
|
||||||
getLogger(ConversationViewModel.class.getName());
|
getLogger(ConversationViewModel.class.getName());
|
||||||
|
private static final String SHOW_ONBOARDING_IMAGE =
|
||||||
|
"showOnboardingImage";
|
||||||
|
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
||||||
|
"showOnboardingIntroduction";
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
|
@CryptoExecutor
|
||||||
|
private final Executor cryptoExecutor;
|
||||||
|
// TODO replace with TransactionManager once it exists
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
|
private final SettingsManager settingsManager;
|
||||||
|
private final PrivateMessageFactory privateMessageFactory;
|
||||||
|
private final AttachmentController attachmentController;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ContactId contactId = null;
|
private ContactId contactId = null;
|
||||||
@@ -46,36 +84,64 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
Transformations.map(contact, c -> c.getAuthor().getId());
|
Transformations.map(contact, c -> c.getAuthor().getId());
|
||||||
private final LiveData<String> contactName =
|
private final LiveData<String> contactName =
|
||||||
Transformations.map(contact, UiUtils::getContactDisplayName);
|
Transformations.map(contact, UiUtils::getContactDisplayName);
|
||||||
|
private final MutableLiveData<Boolean> imageSupport =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> showImageOnboarding =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> showIntroductionOnboarding =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> showIntroductionAction =
|
||||||
|
new MutableLiveData<>();
|
||||||
private final MutableLiveData<Boolean> contactDeleted =
|
private final MutableLiveData<Boolean> contactDeleted =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<GroupId> messagingGroupId =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<PrivateMessageHeader> addedHeader =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConversationViewModel(Application application,
|
ConversationViewModel(Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
ContactManager contactManager) {
|
@CryptoExecutor Executor cryptoExecutor, DatabaseComponent db,
|
||||||
|
MessagingManager messagingManager, ContactManager contactManager,
|
||||||
|
SettingsManager settingsManager,
|
||||||
|
PrivateMessageFactory privateMessageFactory) {
|
||||||
super(application);
|
super(application);
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
|
this.cryptoExecutor = cryptoExecutor;
|
||||||
|
this.db = db;
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
|
this.settingsManager = settingsManager;
|
||||||
|
this.privateMessageFactory = privateMessageFactory;
|
||||||
|
this.attachmentController = new AttachmentController(messagingManager,
|
||||||
|
getAttachmentDimensions(application.getResources()));
|
||||||
contactDeleted.setValue(false);
|
contactDeleted.setValue(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting the {@link ContactId} automatically triggers loading of other
|
||||||
|
* data.
|
||||||
|
*/
|
||||||
void setContactId(ContactId contactId) {
|
void setContactId(ContactId contactId) {
|
||||||
if (this.contactId == null) {
|
if (this.contactId == null) {
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
loadContact();
|
loadContact(contactId);
|
||||||
} else if (!contactId.equals(this.contactId)) {
|
} else if (!contactId.equals(this.contactId)) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadContact() {
|
private void loadContact(ContactId contactId) {
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
Contact c =
|
Contact c = contactManager.getContact(contactId);
|
||||||
contactManager.getContact(requireNonNull(contactId));
|
|
||||||
contact.postValue(c);
|
contact.postValue(c);
|
||||||
logDuration(LOG, "Loading contact", start);
|
logDuration(LOG, "Loading contact", start);
|
||||||
|
start = now();
|
||||||
|
checkFeaturesAndOnboarding(contactId);
|
||||||
|
logDuration(LOG, "Checking for image support", start);
|
||||||
} catch (NoSuchContactException e) {
|
} catch (NoSuchContactException e) {
|
||||||
contactDeleted.postValue(true);
|
contactDeleted.postValue(true);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
@@ -89,13 +155,183 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
try {
|
try {
|
||||||
contactManager.setContactAlias(requireNonNull(contactId),
|
contactManager.setContactAlias(requireNonNull(contactId),
|
||||||
alias.isEmpty() ? null : alias);
|
alias.isEmpty() ? null : alias);
|
||||||
loadContact();
|
loadContact(contactId);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendMessage(@Nullable String text, List<Uri> uris, long timestamp) {
|
||||||
|
if (messagingGroupId.getValue() == null) loadGroupId();
|
||||||
|
observeForeverOnce(messagingGroupId, groupId -> {
|
||||||
|
if (groupId == null) return;
|
||||||
|
// calls through to creating and storing the message
|
||||||
|
storeAttachments(groupId, text, uris, timestamp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadGroupId() {
|
||||||
|
if (contactId == null) throw new IllegalStateException();
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
messagingGroupId.postValue(
|
||||||
|
messagingManager.getConversationId(contactId));
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
|
||||||
|
// check if images are supported
|
||||||
|
boolean imagesSupported = db.transactionWithResult(true, txn ->
|
||||||
|
messagingManager.contactSupportsImages(txn, c));
|
||||||
|
imageSupport.postValue(imagesSupported);
|
||||||
|
|
||||||
|
// check if introductions are supported
|
||||||
|
Collection<Contact> contacts = contactManager.getActiveContacts();
|
||||||
|
boolean introductionSupported = contacts.size() > 1;
|
||||||
|
showIntroductionAction.postValue(introductionSupported);
|
||||||
|
|
||||||
|
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||||
|
if (imagesSupported &&
|
||||||
|
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
|
||||||
|
// check if we should show onboarding, only if images are supported
|
||||||
|
showImageOnboarding.postValue(true);
|
||||||
|
// allow observer to stop listening for changes
|
||||||
|
showIntroductionOnboarding.postValue(false);
|
||||||
|
} else {
|
||||||
|
// allow observer to stop listening for changes
|
||||||
|
showImageOnboarding.postValue(false);
|
||||||
|
// we only show one onboarding dialog at a time
|
||||||
|
if (introductionSupported &&
|
||||||
|
settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION, true)) {
|
||||||
|
showIntroductionOnboarding.postValue(true);
|
||||||
|
} else {
|
||||||
|
// allow observer to stop listening for changes
|
||||||
|
showIntroductionOnboarding.postValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onImageOnboardingSeen() {
|
||||||
|
onOnboardingSeen(SHOW_ONBOARDING_IMAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIntroductionOnboardingSeen() {
|
||||||
|
onOnboardingSeen(SHOW_ONBOARDING_INTRODUCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onOnboardingSeen(String key) {
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
Settings settings = new Settings();
|
||||||
|
settings.putBoolean(key, false);
|
||||||
|
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeAttachments(GroupId groupId, @Nullable String text,
|
||||||
|
List<Uri> uris, long timestamp) {
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
long start = now();
|
||||||
|
List<AttachmentHeader> attachments = new ArrayList<>();
|
||||||
|
List<AttachmentItem> items = new ArrayList<>();
|
||||||
|
boolean needsSize = uris.size() == 1;
|
||||||
|
for (Uri uri : uris) {
|
||||||
|
Pair<AttachmentHeader, AttachmentItem> pair =
|
||||||
|
createAttachmentHeader(groupId, uri, timestamp,
|
||||||
|
needsSize);
|
||||||
|
if (pair == null) continue;
|
||||||
|
attachments.add(pair.getFirst());
|
||||||
|
items.add(pair.getSecond());
|
||||||
|
}
|
||||||
|
logDuration(LOG, "Storing attachments", start);
|
||||||
|
createMessage(groupId, text, attachments, items, timestamp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@DatabaseExecutor
|
||||||
|
private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader(
|
||||||
|
GroupId groupId, Uri uri, long timestamp, boolean needsSize) {
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
ContentResolver contentResolver =
|
||||||
|
getApplication().getContentResolver();
|
||||||
|
is = contentResolver.openInputStream(uri);
|
||||||
|
if (is == null) throw new IOException();
|
||||||
|
String contentType = contentResolver.getType(uri);
|
||||||
|
if (contentType == null) throw new IOException("null content type");
|
||||||
|
AttachmentHeader h = messagingManager
|
||||||
|
.addLocalAttachment(groupId, timestamp, contentType, is);
|
||||||
|
is.close();
|
||||||
|
// re-open stream to get AttachmentItem
|
||||||
|
is = contentResolver.openInputStream(uri);
|
||||||
|
if (is == null) throw new IOException();
|
||||||
|
AttachmentItem item = attachmentController
|
||||||
|
.getAttachmentItem(h, new Attachment(is), needsSize);
|
||||||
|
return new Pair<>(h, item);
|
||||||
|
} catch (DbException | IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (is != null) tryToClose(is, LOG, WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createMessage(GroupId groupId, @Nullable String text,
|
||||||
|
List<AttachmentHeader> attachments, List<AttachmentItem> aItems,
|
||||||
|
long timestamp) {
|
||||||
|
cryptoExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
// TODO remove when text can be null in the backend
|
||||||
|
String msgText = text == null ? "null" : text;
|
||||||
|
PrivateMessage pm = privateMessageFactory
|
||||||
|
.createPrivateMessage(groupId, timestamp, msgText,
|
||||||
|
attachments);
|
||||||
|
attachmentController.put(pm.getMessage().getId(), aItems);
|
||||||
|
storeMessage(pm, msgText, attachments);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeMessage(PrivateMessage m, @Nullable String text,
|
||||||
|
List<AttachmentHeader> attachments) {
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
long start = now();
|
||||||
|
messagingManager.addLocalMessage(m);
|
||||||
|
logDuration(LOG, "Storing message", start);
|
||||||
|
Message message = m.getMessage();
|
||||||
|
PrivateMessageHeader h = new PrivateMessageHeader(
|
||||||
|
message.getId(), message.getGroupId(),
|
||||||
|
message.getTimestamp(), true, true, false, false,
|
||||||
|
text != null, attachments);
|
||||||
|
// TODO add text to cache when available here
|
||||||
|
addedHeader.postValue(h);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
void onAddedPrivateMessageSeen() {
|
||||||
|
addedHeader.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentController getAttachmentController() {
|
||||||
|
return attachmentController;
|
||||||
|
}
|
||||||
|
|
||||||
LiveData<Contact> getContact() {
|
LiveData<Contact> getContact() {
|
||||||
return contact;
|
return contact;
|
||||||
}
|
}
|
||||||
@@ -108,8 +344,28 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
return contactName;
|
return contactName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> hasImageSupport() {
|
||||||
|
return imageSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> showImageOnboarding() {
|
||||||
|
return showImageOnboarding;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> showIntroductionOnboarding() {
|
||||||
|
return showIntroductionOnboarding;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> showIntroductionAction() {
|
||||||
|
return showIntroductionAction;
|
||||||
|
}
|
||||||
|
|
||||||
LiveData<Boolean> isContactDeleted() {
|
LiveData<Boolean> isContactDeleted() {
|
||||||
return contactDeleted;
|
return contactDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveData<PrivateMessageHeader> getAddedPrivateMessage() {
|
||||||
|
return addedHeader;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ import org.briarproject.briar.api.forum.ForumInvitationRequest;
|
|||||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.BLOG;
|
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.BLOG;
|
||||||
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.FORUM;
|
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.FORUM;
|
||||||
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.GROUP;
|
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.GROUP;
|
||||||
@@ -33,24 +37,33 @@ class ConversationVisitor implements
|
|||||||
|
|
||||||
private final Context ctx;
|
private final Context ctx;
|
||||||
private final TextCache textCache;
|
private final TextCache textCache;
|
||||||
|
private final AttachmentCache attachmentCache;
|
||||||
private final LiveData<String> contactName;
|
private final LiveData<String> contactName;
|
||||||
|
|
||||||
ConversationVisitor(Context ctx, TextCache textCache,
|
ConversationVisitor(Context ctx, TextCache textCache,
|
||||||
LiveData<String> contactName) {
|
AttachmentCache attachmentCache, LiveData<String> contactName) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.textCache = textCache;
|
this.textCache = textCache;
|
||||||
|
this.attachmentCache = attachmentCache;
|
||||||
this.contactName = contactName;
|
this.contactName = contactName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConversationItem visitPrivateMessageHeader(PrivateMessageHeader h) {
|
public ConversationItem visitPrivateMessageHeader(PrivateMessageHeader h) {
|
||||||
ConversationItem item;
|
ConversationItem item;
|
||||||
|
List<AttachmentItem> attachments;
|
||||||
|
if (h.getAttachmentHeaders().isEmpty()) {
|
||||||
|
attachments = emptyList();
|
||||||
|
} else {
|
||||||
|
attachments = attachmentCache
|
||||||
|
.getAttachmentItems(h.getId(), h.getAttachmentHeaders());
|
||||||
|
}
|
||||||
if (h.isLocal()) {
|
if (h.isLocal()) {
|
||||||
item = new ConversationMessageItem(
|
item = new ConversationMessageItem(
|
||||||
R.layout.list_item_conversation_msg_out, h);
|
R.layout.list_item_conversation_msg_out, h, attachments);
|
||||||
} else {
|
} else {
|
||||||
item = new ConversationMessageItem(
|
item = new ConversationMessageItem(
|
||||||
R.layout.list_item_conversation_msg_in, h);
|
R.layout.list_item_conversation_msg_in, h, attachments);
|
||||||
}
|
}
|
||||||
if (h.hasText()) {
|
if (h.hasText()) {
|
||||||
String text = textCache.getText(h.getId());
|
String text = textCache.getText(h.getId());
|
||||||
@@ -279,4 +292,9 @@ class ConversationVisitor implements
|
|||||||
@Nullable
|
@Nullable
|
||||||
String getText(MessageId m);
|
String getText(MessageId m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AttachmentCache {
|
||||||
|
List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||||
|
List<AttachmentHeader> headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,326 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.support.design.widget.AppBarLayout;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v7.app.AlertDialog.Builder;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.transition.Fade;
|
||||||
|
import android.transition.Transition;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.view.PullDownLayout;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.content.Intent.ACTION_CREATE_DOCUMENT;
|
||||||
|
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||||
|
import static android.content.Intent.EXTRA_TITLE;
|
||||||
|
import static android.graphics.Color.TRANSPARENT;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
|
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||||
|
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class ImageActivity extends BriarActivity
|
||||||
|
implements PullDownLayout.Callback, OnGlobalLayoutListener {
|
||||||
|
|
||||||
|
final static String ATTACHMENTS = "attachments";
|
||||||
|
final static String ATTACHMENT_POSITION = "position";
|
||||||
|
final static String NAME = "name";
|
||||||
|
final static String DATE = "date";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private ImageViewModel viewModel;
|
||||||
|
private PullDownLayout layout;
|
||||||
|
private AppBarLayout appBarLayout;
|
||||||
|
private ViewPager viewPager;
|
||||||
|
private List<AttachmentItem> attachments;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
if (state == null) supportPostponeEnterTransition();
|
||||||
|
Window window = getWindow();
|
||||||
|
if (SDK_INT >= 21) {
|
||||||
|
Transition transition = new Fade();
|
||||||
|
setSceneTransitionAnimation(transition, null, transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get View Model
|
||||||
|
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||||
|
.get(ImageViewModel.class);
|
||||||
|
viewModel.getSaveState().observe(this, this::onImageSaveStateChanged);
|
||||||
|
|
||||||
|
// inflate layout
|
||||||
|
setContentView(R.layout.activity_image);
|
||||||
|
layout = findViewById(R.id.layout);
|
||||||
|
layout.setCallback(this);
|
||||||
|
layout.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||||
|
|
||||||
|
// Status Bar
|
||||||
|
if (SDK_INT >= 21) {
|
||||||
|
window.setStatusBarColor(TRANSPARENT);
|
||||||
|
} else if (SDK_INT >= 19) {
|
||||||
|
// we can't make the status bar transparent, but translucent
|
||||||
|
window.addFlags(FLAG_TRANSLUCENT_STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
appBarLayout = findViewById(R.id.appBarLayout);
|
||||||
|
Toolbar toolbar = requireNonNull(setUpCustomToolbar(true));
|
||||||
|
TextView contactName = toolbar.findViewById(R.id.contactName);
|
||||||
|
TextView dateView = toolbar.findViewById(R.id.dateView);
|
||||||
|
|
||||||
|
// Intent Extras
|
||||||
|
Intent i = getIntent();
|
||||||
|
attachments = i.getParcelableArrayListExtra(ATTACHMENTS);
|
||||||
|
int position = i.getIntExtra(ATTACHMENT_POSITION, -1);
|
||||||
|
if (position == -1) throw new IllegalStateException();
|
||||||
|
String name = i.getStringExtra(NAME);
|
||||||
|
long time = i.getLongExtra(DATE, 0);
|
||||||
|
String date = formatDateAbsolute(this, time);
|
||||||
|
contactName.setText(name);
|
||||||
|
dateView.setText(date);
|
||||||
|
|
||||||
|
// Set up image ViewPager
|
||||||
|
viewPager = findViewById(R.id.viewPager);
|
||||||
|
ImagePagerAdapter pagerAdapter =
|
||||||
|
new ImagePagerAdapter(getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(pagerAdapter);
|
||||||
|
viewPager.setCurrentItem(position);
|
||||||
|
|
||||||
|
if (SDK_INT >= 16) {
|
||||||
|
viewModel.getOnImageClicked().observe(this, this::onImageClicked);
|
||||||
|
window.getDecorView().setSystemUiVisibility(
|
||||||
|
SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||||
|
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.image_actions, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
case R.id.action_save_image:
|
||||||
|
showSaveImageDialog();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
viewModel.setToolbarPosition(
|
||||||
|
appBarLayout.getTop(), appBarLayout.getBottom()
|
||||||
|
);
|
||||||
|
if (SDK_INT >= 16) {
|
||||||
|
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||||
|
} else {
|
||||||
|
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int request, int result,
|
||||||
|
@Nullable Intent data) {
|
||||||
|
super.onActivityResult(request, result, data);
|
||||||
|
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK &&
|
||||||
|
data != null) {
|
||||||
|
viewModel.saveImage(getVisibleAttachment(), data.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPullStart() {
|
||||||
|
appBarLayout.animate()
|
||||||
|
.alpha(0f)
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPull(float progress) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPullCancel() {
|
||||||
|
appBarLayout.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPullComplete() {
|
||||||
|
supportFinishAfterTransition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 16)
|
||||||
|
private void onImageClicked(@Nullable Boolean clicked) {
|
||||||
|
if (clicked != null && clicked) {
|
||||||
|
toggleSystemUi();
|
||||||
|
viewModel.onOnImageClickSeen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 16)
|
||||||
|
private void toggleSystemUi() {
|
||||||
|
View decorView = getWindow().getDecorView();
|
||||||
|
if (appBarLayout.getVisibility() == VISIBLE) {
|
||||||
|
hideSystemUi(decorView);
|
||||||
|
} else {
|
||||||
|
showSystemUi(decorView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 16)
|
||||||
|
private void hideSystemUi(View decorView) {
|
||||||
|
decorView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN |
|
||||||
|
SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
);
|
||||||
|
appBarLayout.animate()
|
||||||
|
.translationYBy(-1 * appBarLayout.getHeight())
|
||||||
|
.alpha(0f)
|
||||||
|
.withEndAction(() -> appBarLayout.setVisibility(GONE))
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 16)
|
||||||
|
private void showSystemUi(View decorView) {
|
||||||
|
decorView.setSystemUiVisibility(
|
||||||
|
SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
);
|
||||||
|
appBarLayout.animate()
|
||||||
|
.translationYBy(appBarLayout.getHeight())
|
||||||
|
.alpha(1f)
|
||||||
|
.withStartAction(() -> appBarLayout.setVisibility(VISIBLE))
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSaveImageDialog() {
|
||||||
|
OnClickListener okListener = (dialog, which) -> {
|
||||||
|
if (SDK_INT >= 19) {
|
||||||
|
Intent intent = getCreationIntent();
|
||||||
|
startActivityForResult(intent, REQUEST_SAVE_ATTACHMENT);
|
||||||
|
} else {
|
||||||
|
viewModel.saveImage(getVisibleAttachment());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||||
|
builder.setTitle(getString(R.string.dialog_title_save_image));
|
||||||
|
builder.setMessage(getString(R.string.dialog_message_save_image));
|
||||||
|
Drawable icon = ContextCompat.getDrawable(this, R.drawable.ic_security);
|
||||||
|
DrawableCompat.setTint(requireNonNull(icon),
|
||||||
|
ContextCompat.getColor(this, R.color.color_primary));
|
||||||
|
builder.setIcon(icon);
|
||||||
|
builder.setPositiveButton(R.string.save_image, okListener);
|
||||||
|
builder.setNegativeButton(R.string.cancel, null);
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 19)
|
||||||
|
private Intent getCreationIntent() {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
|
||||||
|
Locale.getDefault());
|
||||||
|
String fileName = sdf.format(new Date());
|
||||||
|
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
|
||||||
|
intent.addCategory(CATEGORY_OPENABLE);
|
||||||
|
intent.setType(getVisibleAttachment().getMimeType());
|
||||||
|
intent.putExtra(EXTRA_TITLE, fileName);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onImageSaveStateChanged(@Nullable Boolean error) {
|
||||||
|
if (error == null) return;
|
||||||
|
int stringRes = error ?
|
||||||
|
R.string.save_image_error : R.string.save_image_success;
|
||||||
|
int colorRes = error ?
|
||||||
|
R.color.briar_red : R.color.briar_primary;
|
||||||
|
Snackbar s = Snackbar.make(layout, stringRes, LENGTH_LONG);
|
||||||
|
s.getView().setBackgroundResource(colorRes);
|
||||||
|
s.show();
|
||||||
|
viewModel.onSaveStateSeen();
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentItem getVisibleAttachment() {
|
||||||
|
return attachments.get(viewPager.getCurrentItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ImagePagerAdapter extends FragmentStatePagerAdapter {
|
||||||
|
|
||||||
|
private boolean isFirst = true;
|
||||||
|
|
||||||
|
private ImagePagerAdapter(FragmentManager fm) {
|
||||||
|
super(fm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
Fragment f = ImageFragment
|
||||||
|
.newInstance(attachments.get(position), isFirst);
|
||||||
|
isFirst = false;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return attachments.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.widget.RecyclerView.Adapter;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static android.content.Context.WINDOW_SERVICE;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.isRtl;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImageAdapter extends Adapter<ImageViewHolder> {
|
||||||
|
|
||||||
|
private final List<AttachmentItem> items = new ArrayList<>();
|
||||||
|
private final ConversationListener listener;
|
||||||
|
private final int imageSize;
|
||||||
|
private final int radiusBig, radiusSmall;
|
||||||
|
private final boolean isRtl;
|
||||||
|
@Nullable
|
||||||
|
private ConversationMessageItem conversationItem;
|
||||||
|
|
||||||
|
ImageAdapter(Context ctx, ConversationListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
imageSize = getImageSize(ctx);
|
||||||
|
Resources res = ctx.getResources();
|
||||||
|
radiusBig =
|
||||||
|
res.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
|
||||||
|
radiusSmall =
|
||||||
|
res.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
|
||||||
|
isRtl = isRtl(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
|
||||||
|
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
||||||
|
R.layout.list_item_image, viewGroup, false);
|
||||||
|
return new ImageViewHolder(v, imageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ImageViewHolder imageViewHolder,
|
||||||
|
int position) {
|
||||||
|
// get item
|
||||||
|
requireNonNull(conversationItem);
|
||||||
|
AttachmentItem item = items.get(position);
|
||||||
|
// set onClick listener
|
||||||
|
imageViewHolder.itemView.setOnClickListener(v ->
|
||||||
|
listener.onAttachmentClicked(v, conversationItem, item)
|
||||||
|
);
|
||||||
|
// bind view holder
|
||||||
|
int size = items.size();
|
||||||
|
boolean isIncoming = conversationItem.isIncoming();
|
||||||
|
boolean hasText = conversationItem.getText() != null;
|
||||||
|
Radii r = getRadii(position, size, isIncoming, hasText);
|
||||||
|
imageViewHolder.bind(item, r, size == 1, singleInRow(position, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setConversationItem(ConversationMessageItem item) {
|
||||||
|
this.conversationItem = item;
|
||||||
|
this.items.clear();
|
||||||
|
this.items.addAll(item.getAttachments());
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getImageSize(Context ctx) {
|
||||||
|
Resources res = ctx.getResources();
|
||||||
|
WindowManager windowManager =
|
||||||
|
(WindowManager) ctx.getSystemService(WINDOW_SERVICE);
|
||||||
|
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||||
|
if (windowManager == null) {
|
||||||
|
return res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_default);
|
||||||
|
}
|
||||||
|
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
|
||||||
|
int imageSize = displayMetrics.widthPixels / 3;
|
||||||
|
int maxSize = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_max_width);
|
||||||
|
return Math.min(imageSize, maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Radii getRadii(int pos, int num, boolean isIncoming,
|
||||||
|
boolean hasText) {
|
||||||
|
boolean left = isLeft(pos);
|
||||||
|
boolean single = num == 1;
|
||||||
|
// Top Row
|
||||||
|
int topLeft;
|
||||||
|
int topRight;
|
||||||
|
if (single) {
|
||||||
|
topLeft = isIncoming ? radiusSmall : radiusBig;
|
||||||
|
topRight = !isIncoming ? radiusSmall : radiusBig;
|
||||||
|
} else if (isTopRow(pos)) {
|
||||||
|
topLeft = left ? (isIncoming ? radiusSmall : radiusBig) : 0;
|
||||||
|
topRight = !left ? (!isIncoming ? radiusSmall : radiusBig) : 0;
|
||||||
|
} else {
|
||||||
|
topLeft = 0;
|
||||||
|
topRight = 0;
|
||||||
|
}
|
||||||
|
// Bottom Row
|
||||||
|
boolean singleInRow = singleInRow(pos, num);
|
||||||
|
int bottomLeft;
|
||||||
|
int bottomRight;
|
||||||
|
if (!hasText && isBottomRow(pos, num)) {
|
||||||
|
bottomLeft = singleInRow || left ? radiusBig : 0;
|
||||||
|
bottomRight = singleInRow || !left ? radiusBig : 0;
|
||||||
|
} else {
|
||||||
|
bottomLeft = 0;
|
||||||
|
bottomRight = 0;
|
||||||
|
}
|
||||||
|
if (isRtl) return new Radii(topRight, topLeft, bottomRight, bottomLeft);
|
||||||
|
return new Radii(topLeft, topRight, bottomLeft, bottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
items.clear();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isTopRow(int pos) {
|
||||||
|
return pos < 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isLeft(int pos) {
|
||||||
|
return pos % 2 == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isBottomRow(int pos, int num) {
|
||||||
|
return num % 2 == 0 ?
|
||||||
|
pos >= num - 2 : // last two, if even
|
||||||
|
pos > num - 2; // last one, if odd
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean singleInRow(int pos, int num) {
|
||||||
|
// last item of an odd number
|
||||||
|
return num % 2 != 0 && pos == num -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
|
import android.graphics.drawable.Animatable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.github.chrisbanes.photoview.PhotoView;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.widget.ImageView.ScaleType.FIT_START;
|
||||||
|
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public class ImageFragment extends Fragment {
|
||||||
|
|
||||||
|
private final static String IS_FIRST = "isFirst";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private AttachmentItem attachment;
|
||||||
|
private boolean isFirst;
|
||||||
|
private ImageViewModel viewModel;
|
||||||
|
private PhotoView photoView;
|
||||||
|
|
||||||
|
static ImageFragment newInstance(AttachmentItem a, boolean isFirst) {
|
||||||
|
ImageFragment f = new ImageFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putParcelable(ATTACHMENT_POSITION, a);
|
||||||
|
args.putBoolean(IS_FIRST, isFirst);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
BaseActivity a = (BaseActivity) requireNonNull(getActivity());
|
||||||
|
a.getActivityComponent().inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Bundle args = requireNonNull(getArguments());
|
||||||
|
attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION));
|
||||||
|
isFirst = args.getBoolean(IS_FIRST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_image, container,
|
||||||
|
false);
|
||||||
|
|
||||||
|
viewModel = ViewModelProviders.of(requireNonNull(getActivity()),
|
||||||
|
viewModelFactory).get(ImageViewModel.class);
|
||||||
|
|
||||||
|
photoView = v.findViewById(R.id.photoView);
|
||||||
|
photoView.setScaleLevels(1, 2, 4);
|
||||||
|
photoView.setOnClickListener(view -> viewModel.clickImage());
|
||||||
|
|
||||||
|
// Request Listener
|
||||||
|
RequestListener<Drawable> listener = new RequestListener<Drawable>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e,
|
||||||
|
Object model, Target<Drawable> target,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
if (getActivity() != null && isFirst)
|
||||||
|
getActivity().supportStartPostponedEnterTransition();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Drawable resource, Object model,
|
||||||
|
Target<Drawable> target, DataSource dataSource,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
|
||||||
|
// set transition name only when not animatable,
|
||||||
|
// because the animation won't start otherwise
|
||||||
|
photoView.setTransitionName(
|
||||||
|
attachment.getTransitionName());
|
||||||
|
}
|
||||||
|
// Move image to the top if overlapping toolbar
|
||||||
|
if (viewModel.isOverlappingToolbar(photoView, resource)) {
|
||||||
|
photoView.setScaleType(FIT_START);
|
||||||
|
}
|
||||||
|
if (getActivity() != null && isFirst) {
|
||||||
|
getActivity().supportStartPostponedEnterTransition();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load Image
|
||||||
|
GlideApp.with(this)
|
||||||
|
.load(attachment)
|
||||||
|
// TODO allow if size < maxTextureSize ?
|
||||||
|
// .override(SIZE_ORIGINAL)
|
||||||
|
.diskCacheStrategy(NONE)
|
||||||
|
.error(R.drawable.ic_image_broken)
|
||||||
|
.addListener(listener)
|
||||||
|
.into(photoView);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface ImageHelper {
|
||||||
|
|
||||||
|
DecodeResult decodeStream(InputStream is);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getExtensionFromMimeType(String mimeType);
|
||||||
|
|
||||||
|
class DecodeResult {
|
||||||
|
|
||||||
|
final int width;
|
||||||
|
final int height;
|
||||||
|
final String mimeType;
|
||||||
|
|
||||||
|
DecodeResult(int width, int height, String mimeType) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.RecyclerView.ItemDecoration;
|
||||||
|
import android.support.v7.widget.RecyclerView.State;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageAdapter.isBottomRow;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageAdapter.isLeft;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageAdapter.isTopRow;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageAdapter.singleInRow;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.isRtl;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImageItemDecoration extends ItemDecoration {
|
||||||
|
|
||||||
|
private final int border;
|
||||||
|
private final boolean isRtl;
|
||||||
|
|
||||||
|
ImageItemDecoration(Context ctx) {
|
||||||
|
Resources res = ctx.getResources();
|
||||||
|
|
||||||
|
// for pixel perfection, add a pixel to the border if it has an odd size
|
||||||
|
int b = res.getDimensionPixelSize(R.dimen.message_bubble_border);
|
||||||
|
int realBorderSize = b % 2 == 0 ? b : b + 1;
|
||||||
|
|
||||||
|
// we are applying half the border around the insides of each image
|
||||||
|
// to prevent differently sized images looking slightly broken
|
||||||
|
border = realBorderSize / 2;
|
||||||
|
|
||||||
|
// find out if we are showing a RTL language
|
||||||
|
isRtl = isRtl(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||||
|
State state) {
|
||||||
|
if (state.getItemCount() == 1) return;
|
||||||
|
int pos = parent.getChildAdapterPosition(view);
|
||||||
|
int num = state.getItemCount();
|
||||||
|
boolean start = isLeft(pos) ^ isRtl;
|
||||||
|
outRect.top = isTopRow(pos) ? 0 : border;
|
||||||
|
outRect.left = start ? 0 : border;
|
||||||
|
outRect.right = start && !singleInRow(pos, num) ? border : 0;
|
||||||
|
outRect.bottom = isBottomRow(pos, num) ? 0 : border;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||||
|
import android.support.v7.widget.StaggeredGridLayoutManager.LayoutParams;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.Transformation;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||||
|
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImageViewHolder extends ViewHolder {
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
||||||
|
|
||||||
|
protected final ImageView imageView;
|
||||||
|
private final int imageSize;
|
||||||
|
|
||||||
|
ImageViewHolder(View v, int imageSize) {
|
||||||
|
super(v);
|
||||||
|
imageView = v.findViewById(R.id.imageView);
|
||||||
|
this.imageSize = imageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(AttachmentItem attachment, Radii r, boolean single,
|
||||||
|
boolean needsStretch) {
|
||||||
|
if (attachment.hasError()) {
|
||||||
|
GlideApp.with(imageView)
|
||||||
|
.clear(imageView);
|
||||||
|
imageView.setImageResource(ERROR_RES);
|
||||||
|
} else {
|
||||||
|
setImageViewDimensions(attachment, single, needsStretch);
|
||||||
|
loadImage(attachment, r);
|
||||||
|
if (SDK_INT >= 21) {
|
||||||
|
imageView.setTransitionName(attachment.getTransitionName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setImageViewDimensions(AttachmentItem a, boolean single,
|
||||||
|
boolean needsStretch) {
|
||||||
|
LayoutParams params = (LayoutParams) imageView.getLayoutParams();
|
||||||
|
int width = needsStretch ? imageSize * 2 : imageSize;
|
||||||
|
params.width = single ? a.getThumbnailWidth() : width;
|
||||||
|
params.height = single ? a.getThumbnailHeight() : imageSize;
|
||||||
|
params.setFullSpan(!single && needsStretch);
|
||||||
|
imageView.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadImage(AttachmentItem a, Radii r) {
|
||||||
|
Transformation<Bitmap> transformation = new BriarImageTransformation(r);
|
||||||
|
GlideApp.with(imageView)
|
||||||
|
.load(a)
|
||||||
|
.diskCacheStrategy(NONE)
|
||||||
|
.error(ERROR_RES)
|
||||||
|
.transform(transformation)
|
||||||
|
.transition(withCrossFade())
|
||||||
|
.into(imageView)
|
||||||
|
.waitForLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.arch.lifecycle.AndroidViewModel;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.media.MediaScannerConnection.scanFile;
|
||||||
|
import static android.os.Environment.DIRECTORY_PICTURES;
|
||||||
|
import static android.os.Environment.getExternalStoragePublicDirectory;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class ImageViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
|
private static Logger LOG = getLogger(ImageViewModel.class.getName());
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
@DatabaseExecutor
|
||||||
|
private final Executor dbExecutor;
|
||||||
|
@IoExecutor
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* true means there was an error saving the image, false if image was saved.
|
||||||
|
*/
|
||||||
|
private final MutableLiveData<Boolean> saveState = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> imageClicked =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
private int toolbarTop, toolbarBottom;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ImageViewModel(Application application,
|
||||||
|
MessagingManager messagingManager,
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
@IoExecutor Executor ioExecutor) {
|
||||||
|
super(application);
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.dbExecutor = dbExecutor;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clickImage() {
|
||||||
|
imageClicked.setValue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveData that is true if the image was clicked,
|
||||||
|
* false if it wasn't.
|
||||||
|
*
|
||||||
|
* Call {@link #onOnImageClickSeen()} after consuming an update.
|
||||||
|
*/
|
||||||
|
LiveData<Boolean> getOnImageClicked() {
|
||||||
|
return imageClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
void onOnImageClickSeen() {
|
||||||
|
imageClicked.setValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setToolbarPosition(int top, int bottom) {
|
||||||
|
toolbarTop = top;
|
||||||
|
toolbarBottom = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isOverlappingToolbar(View screenView, Drawable drawable) {
|
||||||
|
int width = drawable.getIntrinsicWidth();
|
||||||
|
int height = drawable.getIntrinsicHeight();
|
||||||
|
float widthPercentage = screenView.getWidth() / (float) width;
|
||||||
|
float heightPercentage = screenView.getHeight() / (float) height;
|
||||||
|
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||||
|
int realWidth = (int) (width * scaleFactor);
|
||||||
|
int realHeight = (int) (height * scaleFactor);
|
||||||
|
// return if image doesn't use the full width,
|
||||||
|
// because it will be moved to the right otherwise
|
||||||
|
if (realWidth < screenView.getWidth()) return false;
|
||||||
|
int drawableTop = (screenView.getHeight() - realHeight) / 2;
|
||||||
|
return drawableTop < toolbarBottom && drawableTop != toolbarTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveData that is true if there was an error
|
||||||
|
* and false if the image was saved.
|
||||||
|
* It can be null otherwise, if no image was saved recently.
|
||||||
|
*
|
||||||
|
* Call {@link #onSaveStateSeen()} after consuming an update.
|
||||||
|
*/
|
||||||
|
LiveData<Boolean> getSaveState() {
|
||||||
|
return saveState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
void onSaveStateSeen() {
|
||||||
|
saveState.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the attachment to a writeable {@link Uri}.
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
void saveImage(AttachmentItem attachment, @Nullable Uri uri) {
|
||||||
|
if (uri == null) {
|
||||||
|
saveState.setValue(true);
|
||||||
|
} else {
|
||||||
|
saveImage(attachment, () -> getOutputStream(uri), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the attachment on external storage,
|
||||||
|
* assuming the permission was granted during install time.
|
||||||
|
*/
|
||||||
|
void saveImage(AttachmentItem attachment) {
|
||||||
|
File file = getImageFile(attachment);
|
||||||
|
saveImage(attachment, () -> getOutputStream(file), () -> scanFile(
|
||||||
|
getApplication(), new String[] {file.toString()}, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
|
||||||
|
@Nullable Runnable afterCopy) {
|
||||||
|
MessageId messageId = attachment.getMessageId();
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
Attachment a = messagingManager.getAttachment(messageId);
|
||||||
|
copyImageFromDb(a, osp, afterCopy);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
saveState.postValue(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyImageFromDb(Attachment a, OutputStreamProvider osp,
|
||||||
|
@Nullable Runnable afterCopy) {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
InputStream is = a.getStream();
|
||||||
|
OutputStream os = osp.getOutputStream();
|
||||||
|
copyAndClose(is, os);
|
||||||
|
if (afterCopy != null) afterCopy.run();
|
||||||
|
saveState.postValue(false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
saveState.postValue(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFileName() {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
|
||||||
|
Locale.getDefault());
|
||||||
|
return sdf.format(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getImageFile(AttachmentItem attachment) {
|
||||||
|
File path = getExternalStoragePublicDirectory(DIRECTORY_PICTURES);
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
path.mkdirs();
|
||||||
|
String fileName = getFileName();
|
||||||
|
String ext = "." + attachment.getExtension();
|
||||||
|
File file = new File(path, fileName + ext);
|
||||||
|
int i = 1;
|
||||||
|
while (file.exists()) {
|
||||||
|
file = new File(path, fileName + " (" + i + ")" + ext);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getOutputStream(File file) throws IOException {
|
||||||
|
return new FileOutputStream(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getOutputStream(Uri uri) throws IOException {
|
||||||
|
OutputStream os =
|
||||||
|
getApplication().getContentResolver().openOutputStream(uri);
|
||||||
|
if (os == null) throw new IOException();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface OutputStreamProvider {
|
||||||
|
OutputStream getOutputStream() throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Priority;
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.data.DataFetcher;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static com.bumptech.glide.load.DataSource.LOCAL;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class BriarDataFetcher implements DataFetcher<InputStream> {
|
||||||
|
|
||||||
|
private final static Logger LOG =
|
||||||
|
getLogger(BriarDataFetcher.class.getName());
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
@DatabaseExecutor
|
||||||
|
private final Executor dbExecutor;
|
||||||
|
private final AttachmentItem attachment;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private volatile InputStream inputStream;
|
||||||
|
private volatile boolean cancel = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BriarDataFetcher(MessagingManager messagingManager,
|
||||||
|
@DatabaseExecutor Executor dbExecutor, AttachmentItem attachment) {
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.dbExecutor = dbExecutor;
|
||||||
|
this.attachment = attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadData(Priority priority,
|
||||||
|
DataCallback<? super InputStream> callback) {
|
||||||
|
MessageId id = attachment.getMessageId();
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
if (cancel) return;
|
||||||
|
try {
|
||||||
|
inputStream = messagingManager.getAttachment(id).getStream();
|
||||||
|
callback.onDataReady(inputStream);
|
||||||
|
} catch (DbException e) {
|
||||||
|
callback.onLoadFailed(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
|
tryToClose(inputStream, LOG, WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
cancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<InputStream> getDataClass() {
|
||||||
|
return InputStream.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource getDataSource() {
|
||||||
|
return LOCAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class BriarDataFetcherFactory {
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
@DatabaseExecutor
|
||||||
|
private final Executor dbExecutor;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public BriarDataFetcherFactory(MessagingManager messagingManager,
|
||||||
|
@DatabaseExecutor Executor dbExecutor) {
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.dbExecutor = dbExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
BriarDataFetcher createBriarDataFetcher(AttachmentItem model) {
|
||||||
|
return new BriarDataFetcher(messagingManager, dbExecutor, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.GlideBuilder;
|
||||||
|
import com.bumptech.glide.Registry;
|
||||||
|
import com.bumptech.glide.annotation.GlideModule;
|
||||||
|
import com.bumptech.glide.module.AppGlideModule;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static android.util.Log.DEBUG;
|
||||||
|
import static android.util.Log.WARN;
|
||||||
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
|
|
||||||
|
@GlideModule
|
||||||
|
@NotNullByDefault
|
||||||
|
public final class BriarGlideModule extends AppGlideModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerComponents(Context context, Glide glide,
|
||||||
|
Registry registry) {
|
||||||
|
BriarApplication app =
|
||||||
|
(BriarApplication) context.getApplicationContext();
|
||||||
|
BriarModelLoaderFactory factory = new BriarModelLoaderFactory(app);
|
||||||
|
registry.prepend(AttachmentItem.class, InputStream.class, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyOptions(Context context, GlideBuilder builder) {
|
||||||
|
builder.setLogLevel(IS_DEBUG_BUILD ? DEBUG : WARN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isManifestParsingEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.MultiTransformation;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||||
|
|
||||||
|
public class BriarImageTransformation extends MultiTransformation<Bitmap> {
|
||||||
|
|
||||||
|
public BriarImageTransformation(Radii r) {
|
||||||
|
super(new CenterCrop(), new CustomCornersTransformation(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.Options;
|
||||||
|
import com.bumptech.glide.load.model.ModelLoader;
|
||||||
|
import com.bumptech.glide.signature.ObjectKey;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public final class BriarModelLoader
|
||||||
|
implements ModelLoader<AttachmentItem, InputStream> {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BriarDataFetcherFactory dataFetcherFactory;
|
||||||
|
|
||||||
|
BriarModelLoader(BriarApplication app) {
|
||||||
|
app.getApplicationComponent().inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoadData<InputStream> buildLoadData(AttachmentItem model, int width,
|
||||||
|
int height, Options options) {
|
||||||
|
ObjectKey key = new ObjectKey(model.getMessageId());
|
||||||
|
BriarDataFetcher dataFetcher =
|
||||||
|
dataFetcherFactory.createBriarDataFetcher(model);
|
||||||
|
return new LoadData<>(key, dataFetcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handles(AttachmentItem model) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.model.ModelLoader;
|
||||||
|
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||||
|
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class BriarModelLoaderFactory
|
||||||
|
implements ModelLoaderFactory<AttachmentItem, InputStream> {
|
||||||
|
|
||||||
|
private final BriarApplication app;
|
||||||
|
|
||||||
|
BriarModelLoaderFactory(BriarApplication app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModelLoader<AttachmentItem, InputStream> build(
|
||||||
|
MultiModelLoaderFactory multiFactory) {
|
||||||
|
return new BriarModelLoader(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void teardown() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user