Compare commits

..

1 Commits

Author SHA1 Message Date
akwizgran
b0928089ec Deliver test messages as though they arrived from contacts. 2020-11-27 11:30:40 +00:00
364 changed files with 5594 additions and 9622 deletions

View File

@@ -13,7 +13,6 @@
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-api" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" />
</method>

View File

@@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-api" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-api" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-api" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -5,14 +5,14 @@ apply plugin: 'witness'
apply from: 'witness.gradle'
android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10213
versionName "1.2.13"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -1,15 +1,11 @@
package org.briarproject.bramble.network;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
import org.briarproject.bramble.api.event.EventBus;
@@ -23,11 +19,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -45,22 +36,16 @@ import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class AndroidNetworkManager implements NetworkManager, Service {
private static final Logger LOG =
getLogger(AndroidNetworkManager.class.getName());
Logger.getLogger(AndroidNetworkManager.class.getName());
// See android.net.wifi.WifiManager
private static final String WIFI_AP_STATE_CHANGED_ACTION =
@@ -69,8 +54,7 @@ class AndroidNetworkManager implements NetworkManager, Service {
private final TaskScheduler scheduler;
private final EventBus eventBus;
private final Executor eventExecutor;
private final Application app;
private final ConnectivityManager connectivityManager;
private final Context appContext;
private final AtomicReference<Cancellable> connectivityCheck =
new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false);
@@ -83,9 +67,7 @@ class AndroidNetworkManager implements NetworkManager, Service {
this.scheduler = scheduler;
this.eventBus = eventBus;
this.eventExecutor = eventExecutor;
this.app = app;
connectivityManager = (ConnectivityManager)
requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE));
this.appContext = app.getApplicationContext();
}
@Override
@@ -100,82 +82,24 @@ class AndroidNetworkManager implements NetworkManager, Service {
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
app.registerReceiver(networkStateReceiver, filter);
appContext.registerReceiver(networkStateReceiver, filter);
}
@Override
public void stopService() {
if (networkStateReceiver != null)
app.unregisterReceiver(networkStateReceiver);
appContext.unregisterReceiver(networkStateReceiver);
}
@Override
public NetworkStatus getNetworkStatus() {
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
ConnectivityManager cm = (ConnectivityManager)
appContext.getSystemService(CONNECTIVITY_SERVICE);
if (cm == null) throw new AssertionError();
NetworkInfo net = cm.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected();
boolean wifi = false, ipv6Only = false;
if (connected) {
wifi = net.getType() == TYPE_WIFI;
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
else ipv6Only = areAllAvailableNetworksIpv6Only();
}
return new NetworkStatus(connected, wifi, ipv6Only);
}
/**
* Returns true if the
* {@link ConnectivityManager#getActiveNetwork() active network} has an
* IPv6 unicast address and no IPv4 addresses. The active network is
* assumed not to be a loopback interface.
*/
@TargetApi(23)
private boolean isActiveNetworkIpv6Only() {
Network net = connectivityManager.getActiveNetwork();
if (net == null) {
LOG.info("No active network");
return false;
}
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) {
LOG.info("No link properties for active network");
return false;
}
boolean hasIpv6Unicast = false;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
return hasIpv6Unicast;
}
/**
* Returns true if the device has at least one network interface with an
* IPv6 unicast address and no interfaces with IPv4 addresses, excluding
* loopback interfaces and interfaces that are
* {@link NetworkInterface#isUp() down}. If this method returns true and
* the device has internet access then it's via IPv6 only.
*/
private boolean areAllAvailableNetworksIpv6Only() {
try {
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
if (interfaces == null) {
LOG.info("No network interfaces");
return false;
}
boolean hasIpv6Unicast = false;
for (NetworkInterface i : list(interfaces)) {
if (i.isLoopback() || !i.isUp()) continue;
for (InetAddress addr : list(i.getInetAddresses())) {
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
}
return hasIpv6Unicast;
} catch (SocketException e) {
logException(LOG, WARNING, e);
return false;
}
boolean wifi = connected && net.getType() == TYPE_WIFI;
return new NetworkStatus(connected, wifi);
}
private void updateConnectionStatus() {

View File

@@ -6,6 +6,8 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver;
import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Parcel;
import android.os.StrictMode;
@@ -15,10 +17,12 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static android.content.Context.WIFI_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.Secure.ANDROID_ID;
@@ -48,6 +52,15 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id);
Parcel parcel = Parcel.obtain();
WifiManager wm = (WifiManager) appContext.getApplicationContext()
.getSystemService(WIFI_SERVICE);
if (wm != null) {
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
if (configs != null) {
for (WifiConfiguration config : configs)
parcel.writeParcelable(config, 0);
}
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) {
for (BluetoothDevice device : bt.getBondedDevices())

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.system;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.PowerManager;
@@ -105,21 +106,14 @@ class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
private String getWakeLockTag(Context ctx) {
PackageManager pm = ctx.getPackageManager();
if (isInstalled(pm, "com.huawei.powergenie")) {
return "LocationManagerService";
} else if (isInstalled(pm, "com.evenwell.PowerMonitor")) {
return "AudioIn";
for (PackageInfo info : pm.getInstalledPackages(0)) {
String name = info.packageName.toLowerCase();
if (name.startsWith("com.huawei.powergenie")) {
return "LocationManagerService";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
}
}
return ctx.getPackageName();
}
private boolean isInstalled(PackageManager pm, String packageName) {
try {
pm.getPackageInfo(packageName, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}

View File

@@ -6,7 +6,4 @@ package org.briarproject.bramble.api;
public interface FeatureFlags {
boolean shouldEnableImageAttachments();
boolean shouldEnableProfilePictures();
}

View File

@@ -1,6 +0,0 @@
package org.briarproject.bramble.api;
public interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
}

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.db.PendingContactExistsException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -178,11 +179,6 @@ public interface ContactManager {
*/
Collection<Contact> getContacts() throws DbException;
/**
* Returns all contacts.
*/
Collection<Contact> getContacts(Transaction txn) throws DbException;
/**
* Removes a contact and all associated state.
*/
@@ -219,6 +215,16 @@ public interface ContactManager {
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
/**
* Returns the {@link AuthorInfo} for the given author.
*/
AuthorInfo getAuthorInfo(AuthorId a) throws DbException;
/**
* Returns the {@link AuthorInfo} for the given author.
*/
AuthorInfo getAuthorInfo(Transaction txn, AuthorId a) throws DbException;
interface ContactHook {
/**

View File

@@ -1,8 +1,6 @@
package org.briarproject.briar.api.identity;
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -22,18 +20,14 @@ public class AuthorInfo {
private final Status status;
@Nullable
private final String alias;
@Nullable
private final AttachmentHeader avatarHeader;
public AuthorInfo(Status status, @Nullable String alias,
@Nullable AttachmentHeader avatarHeader) {
public AuthorInfo(Status status, @Nullable String alias) {
this.status = status;
this.alias = alias;
this.avatarHeader = avatarHeader;
}
public AuthorInfo(Status status) {
this(status, null, null);
this(status, null);
}
public Status getStatus() {
@@ -45,11 +39,6 @@ public class AuthorInfo {
return alias;
}
@Nullable
public AttachmentHeader getAvatarHeader() {
return avatarHeader;
}
@Override
public int hashCode() {
int hashCode = status.ordinal();
@@ -62,9 +51,6 @@ public class AuthorInfo {
if (!(o instanceof AuthorInfo)) return false;
AuthorInfo info = (AuthorInfo) o;
return status == info.status &&
// aliases are equal
NullSafety.equals(alias, info.alias) &&
// avatars are equal
NullSafety.equals(avatarHeader, info.avatarHeader);
(alias == null ? info.alias == null : alias.equals(info.alias));
}
}

View File

@@ -8,12 +8,11 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
public class NetworkStatus {
private final boolean connected, wifi, ipv6Only;
private final boolean connected, wifi;
public NetworkStatus(boolean connected, boolean wifi, boolean ipv6Only) {
public NetworkStatus(boolean connected, boolean wifi) {
this.connected = connected;
this.wifi = wifi;
this.ipv6Only = ipv6Only;
}
public boolean isConnected() {
@@ -23,8 +22,4 @@ public class NetworkStatus {
public boolean isWifi() {
return wifi;
}
public boolean isIpv6Only() {
return ipv6Only;
}
}

View File

@@ -29,12 +29,4 @@ public class NullSafety {
public static void requireNull(@Nullable Object o) {
if (o != null) throw new AssertionError();
}
/**
* Stand-in for {@code Objects.equals()}.
*/
public static boolean equals(@Nullable Object a, @Nullable Object b) {
return (a == b) || (a != null && a.equals(b));
}
}

View File

@@ -23,8 +23,6 @@ public interface DevReporter {
/**
* Sends any reports previously stored on disk.
*
* @return The number of reports that were sent.
*/
int sendReports();
void sendReports();
}

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.NONE;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class AuthorInfoTest extends BrambleTestCase {
@Test
public void testEquals() {
assertEquals(
new AuthorInfo(NONE),
new AuthorInfo(NONE, null)
);
assertEquals(
new AuthorInfo(NONE, "test"),
new AuthorInfo(NONE, "test")
);
assertNotEquals(
new AuthorInfo(NONE),
new AuthorInfo(VERIFIED)
);
assertNotEquals(
new AuthorInfo(NONE, "test"),
new AuthorInfo(NONE)
);
assertNotEquals(
new AuthorInfo(NONE),
new AuthorInfo(NONE, "test")
);
assertNotEquals(
new AuthorInfo(NONE, "a"),
new AuthorInfo(NONE, "b")
);
}
}

View File

@@ -20,7 +20,9 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeyManager;
@@ -38,6 +40,10 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
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.UNKNOWN;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
@ThreadSafe
@@ -207,11 +213,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
return db.transactionWithResult(true, db::getContacts);
}
@Override
public Collection<Contact> getContacts(Transaction txn) throws DbException {
return db.getContacts(txn);
}
@Override
public void removeContact(ContactId c) throws DbException {
db.transaction(false, txn -> removeContact(txn, c));
@@ -255,6 +256,25 @@ class ContactManagerImpl implements ContactManager, EventListener {
db.removeContact(txn, c);
}
@Override
public AuthorInfo getAuthorInfo(AuthorId a) throws DbException {
return db.transactionWithResult(true, txn -> getAuthorInfo(txn, a));
}
@Override
public AuthorInfo getAuthorInfo(Transaction txn, AuthorId authorId)
throws DbException {
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.getId().equals(authorId))
return new AuthorInfo(OURSELVES);
Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
if (contacts.isEmpty()) return new AuthorInfo(UNKNOWN);
if (contacts.size() > 1) throw new AssertionError();
Contact c = contacts.iterator().next();
if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias());
else return new AuthorInfo(UNVERIFIED, c.getAlias());
}
@Override
public void eventOccurred(Event e) {
if (e instanceof PendingContactStateChangedEvent) {

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.Base32;
import java.security.GeneralSecurityException;
import java.util.Locale;
import java.util.regex.Matcher;
import javax.inject.Inject;
@@ -53,7 +52,7 @@ class PendingContactFactoryImpl implements PendingContactFactory {
byte[] raw = new byte[RAW_LINK_BYTES];
raw[0] = FORMAT_VERSION;
arraycopy(encoded, 0, raw, 1, encoded.length);
return "briar://" + Base32.encode(raw).toLowerCase(Locale.US);
return "briar://" + Base32.encode(raw).toLowerCase();
}
private PublicKey parseHandshakeLink(String link) throws FormatException {

View File

@@ -863,7 +863,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!state.isTorRunning()) return;
boolean online = status.isConnected();
boolean wifi = status.isWifi();
boolean ipv6Only = status.isIpv6Only();
String country = locationUtils.getCurrentCountry();
boolean blocked =
circumventionProvider.isTorProbablyBlocked(country);
@@ -880,8 +879,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi
+ ", IPv6 only: " + ipv6Only);
LOG.info("Online: " + online + ", wifi: " + wifi);
if (country.isEmpty()) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
LOG.info("Charging: " + charging);
@@ -918,8 +916,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (ipv6Only ||
circumventionProvider.needsMeek(country)) {
if (circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges");
enableBridges = true;
useMeek = true;
@@ -945,7 +942,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (enableNetwork) {
enableBridges(enableBridges, useMeek);
enableConnectionPadding(enableConnectionPadding);
useIpv6(ipv6Only);
}
enableNetwork(enableNetwork);
} catch (IOException e) {
@@ -958,11 +954,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
}
private void useIpv6(boolean ipv6Only) throws IOException {
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
}
@ThreadSafe
@NotNullByDefault
protected class PluginState {

View File

@@ -15,9 +15,6 @@ import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@@ -46,9 +43,9 @@ class TransportPropertyValidator extends BdfMessageValidator {
clientHelper.parseAndValidateTransportProperties(dictionary);
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_TRANSPORT_ID, transportId);
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, false);
meta.put("transportId", transportId);
meta.put("version", version);
meta.put("local", false);
return new BdfMessageContext(meta);
}
}

View File

@@ -29,7 +29,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -101,12 +100,11 @@ class DevReporterImpl implements DevReporter, EventListener {
}
@Override
public int sendReports() {
public void sendReports() {
File reportDir = devConfig.getReportDir();
File[] reports = reportDir.listFiles();
int reportsSent = 0;
if (reports == null || reports.length == 0)
return reportsSent; // No reports to send
return; // No reports to send
LOG.info("Sending reports to developers");
for (File f : reports) {
@@ -118,15 +116,13 @@ class DevReporterImpl implements DevReporter, EventListener {
in = new FileInputStream(f);
IoUtils.copyAndClose(in, out);
f.delete();
reportsSent++;
} catch (IOException e) {
LOG.log(WARNING, "Failed to send reports", e);
tryToClose(out, LOG, WARNING);
tryToClose(in, LOG, WARNING);
return reportsSent;
return;
}
}
if (LOG.isLoggable(INFO)) LOG.info(reportsSent + " report(s) sent");
return reportsSent;
LOG.info("Reports sent");
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;

View File

@@ -0,0 +1,6 @@
package org.briarproject.bramble.sync;
interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
}

View File

@@ -8,15 +8,18 @@ import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
@@ -28,6 +31,10 @@ import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
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.UNKNOWN;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
@@ -39,6 +46,7 @@ import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class ContactManagerImplTest extends BrambleMockTestCase {
@@ -204,6 +212,75 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
assertTrue(contactManager.contactExists(remote.getId(), local));
}
@Test
public void testGetAuthorInfo() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(singletonList(contact)));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(contact.getAlias(), authorInfo.getAlias());
}
@Test
public void testGetAuthorInfoTransaction() throws DbException {
Transaction txn = new Transaction(null, true);
// check unknown author
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(emptyList()));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNKNOWN, authorInfo.getStatus());
assertNull(authorInfo.getAlias());
// check unverified contact
checkAuthorInfoContext(txn, remote.getId(), singletonList(contact));
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(contact.getAlias(), authorInfo.getAlias());
// check verified contact
Contact verified = getContact(remote, local, true);
checkAuthorInfoContext(txn, remote.getId(), singletonList(verified));
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(VERIFIED, authorInfo.getStatus());
assertEquals(verified.getAlias(), authorInfo.getAlias());
// check ourselves
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
never(db).getContactsByAuthorId(txn, remote.getId());
}});
authorInfo = contactManager.getAuthorInfo(txn, localAuthor.getId());
assertEquals(OURSELVES, authorInfo.getStatus());
assertNull(authorInfo.getAlias());
}
private void checkAuthorInfoContext(Transaction txn, AuthorId authorId,
Collection<Contact> contacts) throws DbException {
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(contacts));
}});
}
@Test
public void testGetHandshakeLink() throws Exception {
Transaction txn = new Transaction(null, true);

View File

@@ -22,17 +22,6 @@ public class BrambleCoreIntegrationTestModule {
@Provides
FeatureFlags provideFeatureFlags() {
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return true;
}
@Override
public boolean shouldEnableProfilePictures() {
return true;
}
};
return () -> true;
}
}

View File

@@ -5,8 +5,6 @@ import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
@@ -16,8 +14,8 @@ import javax.inject.Inject;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault
@@ -25,7 +23,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class JavaNetworkManager implements NetworkManager {
private static final Logger LOG =
getLogger(JavaNetworkManager.class.getName());
Logger.getLogger(JavaNetworkManager.class.getName());
@Inject
JavaNetworkManager() {
@@ -33,28 +31,26 @@ class JavaNetworkManager implements NetworkManager {
@Override
public NetworkStatus getNetworkStatus() {
boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false;
boolean connected = false;
try {
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
if (interfaces == null) {
LOG.info("No network interfaces");
} else {
if (interfaces != null) {
for (NetworkInterface i : list(interfaces)) {
if (i.isLoopback() || !i.isUp()) continue;
for (InetAddress addr : list(i.getInetAddresses())) {
connected = true;
if (addr instanceof Inet4Address) {
hasIpv4 = true;
} else if (!addr.isMulticastAddress()) {
hasIpv6Unicast = true;
if (i.isLoopback()) continue;
if (i.isUp() && i.getInetAddresses().hasMoreElements()) {
if (LOG.isLoggable(INFO)) {
LOG.info("Interface " + i.getDisplayName() +
" is up with at least one address.");
}
connected = true;
break;
}
}
}
} catch (SocketException e) {
logException(LOG, WARNING, e);
}
return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast);
return new NetworkStatus(connected, false);
}
}

View File

@@ -16,14 +16,14 @@ def getStdout = { command, defaultValue ->
}
android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10213
versionName "1.2.13"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -100,6 +100,7 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc03'
implementation 'ch.acra:acra:4.11'
implementation 'info.guardianproject.panic:panic:1.0'
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:3.0.1'
@@ -108,7 +109,7 @@ dependencies {
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
def glideVersion = '4.11.0'
def glideVersion = '4.10.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
@@ -126,11 +127,10 @@ dependencies {
testImplementation 'androidx.test:runner:1.3.0'
testImplementation 'androidx.test.ext:junit:1.1.2'
testImplementation 'androidx.fragment:fragment-testing:1.2.5'
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'org.mockito:mockito-core:3.1.0'
testImplementation 'junit:junit:4.13.1'
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:$jmockVersion"
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
@@ -142,9 +142,11 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation 'junit:junit:4.13.1'
androidTestScreenshotImplementation 'tools.fastlane:screengrab:2.0.0'
androidTestScreenshotImplementation 'com.jraska:falcon:2.1.1'
androidTestImplementation 'junit:junit:4.12'
androidTestScreenshotImplementation('tools.fastlane:screengrab:1.2.0') {
// workaround for jetifier issue https://issuetracker.google.com/issues/123060356
exclude group: 'com.android.support.test.uiautomator'
}
androidTestScreenshotImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
import javax.inject.Singleton;
@@ -16,7 +15,6 @@ import dagger.Component;
@Component(modules = {
AppModule.class,
AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
BriarAccountModule.class,

View File

@@ -0,0 +1,41 @@
package org.briarproject.briar.android.attachment;
import android.content.res.AssetManager;
import org.junit.Before;
import java.io.IOException;
import java.io.InputStream;
import static androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
abstract class AbstractAttachmentCreationTaskTest {
private final ImageHelper imageHelper = new ImageHelperImpl();
private final ImageSizeCalculator imageSizeCalculator =
new ImageSizeCalculator(imageHelper);
private AttachmentCreationTask task;
@Before
@SuppressWarnings("ConstantConditions") // add real objects when needed
public void setUp() {
task = new AttachmentCreationTask(null,
getApplicationContext().getContentResolver(), null,
imageSizeCalculator, null, null, true);
}
void testCompress(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
task.compressImage(is, contentType);
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -1,17 +0,0 @@
package org.briarproject.briar.android.attachment;
import org.briarproject.briar.android.attachment.media.MediaModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractAttachmentRetrieverComponent {
void inject(AttachmentRetrieverIntegrationTest test);
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment.media;
package org.briarproject.briar.android.attachment;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -9,13 +9,8 @@ import static android.os.Build.VERSION.SDK_INT;
import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class ImageCompressorTest
extends AbstractImageCompressorTest {
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
public class AttachmentCreationTaskTest
extends AbstractAttachmentCreationTaskTest {
@Test
public void testCompressSmallPng() throws Exception {

View File

@@ -1,20 +1,15 @@
package org.briarproject.briar.android.attachment;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.media.ImageHelper;
import org.briarproject.briar.android.attachment.media.ImageSizeCalculator;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
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.InputStream;
import java.util.Random;
import javax.inject.Inject;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static androidx.test.InstrumentationRegistry.getContext;
@@ -29,28 +24,16 @@ public class AttachmentRetrieverIntegrationTest {
private final AttachmentDimensions dimensions = new AttachmentDimensions(
100, 50, 200, 75, 300
);
private final GroupId groupId = new GroupId(getRandomId());
private final MessageId msgId = new MessageId(getRandomId());
@Inject
ImageHelper imageHelper;
@Inject
ImageSizeCalculator imageSizeCalculator;
private final AttachmentRetriever retriever;
public AttachmentRetrieverIntegrationTest() {
AbstractAttachmentRetrieverComponent component =
DaggerAbstractAttachmentRetrieverComponent.builder().build();
component.inject(this);
retriever = new AttachmentRetrieverImpl(null, null, dimensions,
imageHelper, imageSizeCalculator);
}
private final ImageHelper imageHelper = new ImageHelperImpl();
private final AttachmentRetriever retriever =
new AttachmentRetrieverImpl(null, null, dimensions, imageHelper,
new ImageSizeCalculator(imageHelper));
@Test
public void testSmallJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("kitten_small.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -66,7 +49,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testBigJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("kitten_original.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -82,7 +65,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallPngImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/png");
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
InputStream is = getAssetInputStream("kitten.png");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -98,7 +81,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testUberGif() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("uber.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -113,7 +96,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testLottaPixels() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("lottapixel.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -128,7 +111,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testImageIoCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/png");
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
InputStream is = getAssetInputStream("image_io_crash.png");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -143,7 +126,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testGimpCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("gimp_crash.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -158,7 +141,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testOptiPngAfl() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("opti_png_afl.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -173,7 +156,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testLibrawError() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("libraw_error.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -182,7 +165,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallAnimatedGifMaxDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("animated.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -197,7 +180,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallAnimatedGifHugeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("animated2.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -212,7 +195,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallGifLargeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("error_large.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -227,7 +210,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testHighError() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_high.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -242,7 +225,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testWideError() throws Exception {
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_wide.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment.media;
package org.briarproject.briar.android.attachment;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -17,16 +17,11 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class)
public class PngSuiteImageCompressorTest
extends AbstractImageCompressorTest {
public class PngSuiteAttachmentCreationTaskTest
extends AbstractAttachmentCreationTaskTest {
private static final Logger LOG =
getLogger(PngSuiteImageCompressorTest.class.getName());
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
getLogger(PngSuiteAttachmentCreationTaskTest.class.getName());
@Parameters
public static Iterable<String> data() throws IOException {
@@ -39,14 +34,14 @@ public class PngSuiteImageCompressorTest
private final String filename;
public PngSuiteImageCompressorTest(String filename) {
public PngSuiteAttachmentCreationTaskTest(String filename) {
this.filename = filename;
}
@Test
public void testPngSuiteCompress() throws Exception {
assumeTrue(isOptionalTestEnabled(
PngSuiteImageCompressorTest.class));
PngSuiteAttachmentCreationTaskTest.class));
LOG.info("Testing " + filename);
if (filename.startsWith("x")) {
try {

View File

@@ -1,15 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractImageCompressorComponent {
void inject(AbstractImageCompressorTest test);
}

View File

@@ -1,38 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
import static androidx.test.InstrumentationRegistry.getContext;
public abstract class AbstractImageCompressorTest {
@Inject
ImageCompressor imageCompressor;
public AbstractImageCompressorTest() {
AbstractImageCompressorComponent component =
DaggerAbstractImageCompressorComponent.builder().build();
component.inject(this);
}
protected abstract void inject(
AbstractImageCompressorComponent component);
void testCompress(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
imageCompressor.compressImage(is, contentType);
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractImageSizeCalculatorComponent {
void inject(AbstractImageSizeCalculatorTest test);
}

View File

@@ -1,51 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
import static androidx.test.InstrumentationRegistry.getContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public abstract class AbstractImageSizeCalculatorTest {
@Inject
ImageSizeCalculator imageSizeCalculator;
public AbstractImageSizeCalculatorTest() {
AbstractImageSizeCalculatorComponent component =
DaggerAbstractImageSizeCalculatorComponent.builder().build();
component.inject(this);
}
protected abstract void inject(
AbstractImageSizeCalculatorComponent component);
void testCanCalculateSize(String filename, String contentType, int width,
int height) throws IOException {
InputStream is = getAssetManager().open(filename);
Size size = imageSizeCalculator.getSize(is, contentType);
assertFalse(size.hasError());
assertEquals(width, size.getWidth());
assertEquals(height, size.getHeight());
}
void testCannotCalculateSize(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
Size size = imageSizeCalculator.getSize(is, contentType);
assertTrue(size.hasError());
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -1,80 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static android.os.Build.VERSION.SDK_INT;
import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class ImageSizeCalculatorTest
extends AbstractImageSizeCalculatorTest {
@Override
protected void inject(AbstractImageSizeCalculatorComponent component) {
component.inject(this);
}
@Test
public void testCalculateSizeKittenSmall() throws Exception {
testCanCalculateSize("kitten_small.jpg", "image/jpeg", 160, 240);
}
@Test
public void testCalculateSizeKittenSmallNoExif() throws Exception {
testCanCalculateSize("kitten_small_noexif.jpg", "image/jpeg", 160, 240);
}
@Test
public void testCalculateSizeSmallPng() throws Exception {
testCanCalculateSize("red-100x100.png", "image/png", 100, 100);
}
@Test
public void testCalculateSizeMediumPng() throws Exception {
testCanCalculateSize("blue-500x500.png", "image/png", 500, 500);
}
@Test
public void testCalculateSizeLargePng() throws Exception {
testCanCalculateSize("green-1000x2000.png", "image/png", 1000, 2000);
}
@Test
public void testCalculateSizeTransparentPng() throws Exception {
testCanCalculateSize("transparent-100x100.png", "image/png", 100, 100);
}
@Test
public void testCalculateSizeVeryHighJpg() throws Exception {
testCanCalculateSize("error_high.jpg", "image/jpeg", 1, 10000);
}
@Test
public void testCalculateSizeVeryWideJpg() throws Exception {
testCanCalculateSize("error_wide.jpg", "image/jpeg", 1920, 1);
}
@Test
public void testCalculateSizeAnimatedGif1() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("animated.gif", "image/gif", 65535, 65535);
}
@Test
public void testCalculateSizeAnimatedGif2() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("animated2.gif", "image/gif", 10000, 10000);
}
@Test
public void testCalculateSizeVeryLargeGif() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("error_large.gif", "image/gif", 16384, 16384);
}
}

View File

@@ -1,80 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class)
public class PngSuiteImageSizeCalculatorTest
extends AbstractImageSizeCalculatorTest {
private static final Logger LOG =
getLogger(PngSuiteImageSizeCalculatorTest.class.getName());
@Override
protected void inject(AbstractImageSizeCalculatorComponent component) {
component.inject(this);
}
@Parameters
public static Iterable<String> data() throws IOException {
List<String> data = new ArrayList<>();
String[] files = requireNonNull(getAssetManager().list("PngSuite"));
for (String filename : files)
if (filename.endsWith(".png")) data.add(filename);
return data;
}
private final String filename;
public PngSuiteImageSizeCalculatorTest(String filename) {
this.filename = filename;
}
// some files have sizes other than 32x32
private Map<String, Size> customSizes = new HashMap<>();
{
customSizes.put("cdfn2c08.png", new Size(8, 32, "image/png"));
customSizes.put("cdhn2c08.png", new Size(32, 8, "image/png"));
customSizes.put("cdsn2c08.png", new Size(8, 8, "image/png"));
customSizes.put("PngSuite.png", new Size(256, 256, "image/png"));
}
@Test
public void testPngSuiteCalculateSizes() throws Exception {
assumeTrue(isOptionalTestEnabled(
PngSuiteImageSizeCalculatorTest.class));
LOG.info("Testing " + filename);
if (filename.startsWith("x") && !filename.equals("xcsn0g01.png")) {
testCannotCalculateSize("PngSuite/" + filename, "image/png");
} else if (filename.startsWith("s")) {
int size = Integer.parseInt(filename.substring(1, 3));
testCanCalculateSize("PngSuite/" + filename, "image/png", size,
size);
} else {
int width = 32;
int height = 32;
if (customSizes.containsKey(filename)) {
Size size = customSizes.get(filename);
width = size.getWidth();
height = size.getHeight();
}
testCanCalculateSize("PngSuite/" + filename, "image/png", width,
height);
}
}
}

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.ConversationActivityScreenshotTest;
import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest;
@@ -17,7 +16,6 @@ import dagger.Component;
@Component(modules = {
AppModule.class,
AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
BriarAccountModule.class,
@@ -28,7 +26,6 @@ public interface BriarUiTestComponent extends AndroidComponent {
void inject(SetupDataTest test);
void inject(ConversationActivityScreenshotTest test);
void inject(SettingsActivityScreenshotTest test);
}

View File

@@ -116,7 +116,7 @@ public class SetupDataTest extends ScreenshotTest {
throws DbException {
Context ctx = getApplicationContext();
String bobName = ctx.getString(R.string.screenshot_bob);
Contact bob = testDataCreator.addContact(bobName, true);
Contact bob = testDataCreator.addContact(bobName);
// TODO add messages

View File

@@ -1,105 +0,0 @@
/*
* Some code was taken from:
*
* RIG Random Image Generator
* https://github.com/stedi-akk/RandomImageGenerator
* licenced under Apache2 license.
*/
package org.briarproject.briar.android.test;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import javax.annotation.Nullable;
import javax.inject.Inject;
public class TestAvatarCreatorImpl implements TestAvatarCreator {
private final int WIDTH = 800;
private final int HEIGHT = 640;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final float[] hsv = new float[3];
private final Random random = new Random();
private final ImageCompressor imageCompressor;
@Inject
TestAvatarCreatorImpl(ImageCompressor imageCompressor) {
this.imageCompressor = imageCompressor;
}
@Nullable
@Override
public InputStream getAvatarInputStream() throws IOException {
Bitmap bitmap = generateBitmap();
return imageCompressor.compressImage(bitmap);
}
private Bitmap generateBitmap() {
// one pattern is boring, let's at least use two
if (random.nextBoolean()) {
return generateColoredPixels();
} else {
return generateColoredCircles();
}
}
private Bitmap generateColoredPixels() {
Bitmap bitmap = getBitmapWithRandomBackground();
Canvas canvas = new Canvas(bitmap);
Rect pixel = new Rect();
int pixelMultiplier = random.nextInt(500) + 1;
for (int x = 0; x < WIDTH; x += pixelMultiplier) {
for (int y = 0; y < HEIGHT; y += pixelMultiplier) {
pixel.set(x, y, x + pixelMultiplier, y + pixelMultiplier);
paint.setColor(getRandomColor());
canvas.drawRect(pixel, paint);
}
}
return bitmap;
}
private Bitmap generateColoredCircles() {
Bitmap bitmap = getBitmapWithRandomBackground();
int biggestSide = Math.max(WIDTH, HEIGHT);
int selectedCount = random.nextInt(10) + 2;
Canvas canvas = new Canvas(bitmap);
float radiusFrom = biggestSide / 12f;
float radiusTo = biggestSide / 4f;
for (int i = 0; i < selectedCount; i++) {
float cx = random.nextInt(WIDTH);
float cy = random.nextInt(HEIGHT);
float radius =
random.nextInt((int) (radiusTo - radiusFrom)) + radiusFrom;
paint.setColor(getRandomColor());
canvas.drawCircle(cx, cy, radius, paint);
}
return bitmap;
}
private Bitmap getBitmapWithRandomBackground() {
Bitmap bitmap =
Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(getRandomColor());
return bitmap;
}
private int getRandomColor() {
hsv[0] = random.nextInt(360);
hsv[1] = random.nextFloat();
hsv[2] = 1f;
return Color.HSVToColor(hsv);
}
}

View File

@@ -65,43 +65,30 @@
<service
android:name="org.briarproject.briar.android.NotificationCleanupService"
android:exported="false" />
android:exported="false"></service>
<activity
android:name="org.briarproject.briar.android.reporting.CrashReportActivity"
android:name="org.briarproject.briar.android.reporting.DevReportActivity"
android:excludeFromRecents="true"
android:exported="false"
android:finishOnTaskLaunch="true"
android:label="@string/crash_report_title"
android:launchMode="singleInstance"
android:process=":briar_error_handler"
android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden" />
<activity
android:name="org.briarproject.briar.android.reporting.FeedbackActivity"
android:exported="false"
android:label="@string/feedback_title"
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity"
android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
</activity>
android:windowSoftInputMode="adjustResize|stateHidden"></activity>
<activity
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
android:label="@string/app_name" />
android:label="@string/app_name"></activity>
<activity
android:name="org.briarproject.briar.android.login.StartupActivity"
android:label="@string/app_name" />
android:label="@string/app_name"></activity>
<activity
android:name="org.briarproject.briar.android.account.SetupActivity"
android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
android:windowSoftInputMode="adjustResize|stateAlwaysVisible"></activity>
<activity
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
@@ -359,7 +346,7 @@
<activity
android:name="org.briarproject.briar.android.StartupFailureActivity"
android:label="@string/startup_failed_activity_title" />
android:label="@string/startup_failed_activity_title"></activity>
<activity
android:name="org.briarproject.briar.android.settings.SettingsActivity"
@@ -425,11 +412,11 @@
<activity
android:name="org.briarproject.briar.android.logout.ExitActivity"
android:theme="@android:style/Theme.NoDisplay" />
android:theme="@android:style/Theme.NoDisplay"></activity>
<activity
android:name=".android.logout.HideUiActivity"
android:theme="@android:style/Theme.NoDisplay" />
android:theme="@android:style/Theme.NoDisplay"></activity>
<activity
android:name=".android.account.UnlockActivity"
@@ -449,29 +436,4 @@
android:theme="@style/BriarTheme" />
</application>
<queries>
<package android:name="info.guardianproject.ripple" />
<package android:name="com.huawei.systemmanager" />
<package android:name="com.huawei.powergenie" />
<package android:name="com.evenwell.PowerMonitor" />
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<!-- white-listing the intents below does not seem necessary,
but they are still included in case modified Android versions require it -->
<intent>
<action android:name="android.bluetooth.adapter.action.REQUEST_DISCOVERABLE" />
</intent>
<intent>
<action android:name="android.settings.CHANNEL_NOTIFICATION_SETTINGS" />
</intent>
</queries>
</manifest>

View File

@@ -14,7 +14,6 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
@@ -32,15 +31,14 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.android.view.EmojiTextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.attachment.AttachmentReader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -49,7 +47,6 @@ import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -74,8 +71,7 @@ import dagger.Component;
BrambleAndroidModule.class,
BriarAccountModule.class,
AppModule.class,
AttachmentModule.class,
MediaModule.class
AttachmentModule.class
})
public interface AndroidComponent
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
@@ -90,18 +86,12 @@ public interface AndroidComponent
@DatabaseExecutor
Executor databaseExecutor();
TransactionManager transactionManager();
MessageTracker messageTracker();
LifecycleManager lifecycleManager();
IdentityManager identityManager();
AttachmentReader attachmentReader();
AuthorManager authorManager();
PluginManager pluginManager();
EventBus eventBus();
@@ -183,6 +173,8 @@ public interface AndroidComponent
void inject(BriarService briarService);
void inject(BriarReportSender briarReportSender);
void inject(NotificationCleanupService notificationCleanupService);
void inject(EmojiTextInputView textInputView);

View File

@@ -109,8 +109,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Nullable
private GroupId blockedGroup = null;
private boolean blockSignInReminder = false;
private boolean blockForums = false, blockGroups = false,
blockBlogs = false;
private boolean blockBlogs = false;
private long lastSound = 0;
private volatile Settings settings = new Settings();
@@ -224,8 +223,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
if (s.getNamespace().equals(SETTINGS_NAMESPACE))
settings = s.getSettings();
} else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageReceivedEvent p =
(ConversationMessageReceivedEvent) e;
showContactNotification(p.getContactId());
} else if (e instanceof GroupMessageAddedEvent) {
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
@@ -386,7 +385,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread
private void showGroupMessageNotification(GroupId g) {
if (blockGroups) return;
if (g.equals(blockedGroup)) return;
groupCounts.add(g);
updateGroupMessageNotification(true);
@@ -454,7 +452,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread
private void showForumPostNotification(GroupId g) {
if (blockForums) return;
if (g.equals(blockedGroup)) return;
forumCounts.add(g);
updateForumPostNotification(true);
@@ -684,26 +681,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
@Override
public void blockAllForumPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockForums = true);
}
@Override
public void unblockAllForumPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockForums = false);
}
@Override
public void blockAllGroupMessageNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockGroups = true);
}
@Override
public void unblockAllGroupMessageNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockGroups = false);
}
@Override
public void blockAllBlogPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = true);

View File

@@ -27,26 +27,15 @@ import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.DozeHelperModule;
import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.account.SetupModule;
import org.briarproject.briar.android.contact.ContactListModule;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
import org.briarproject.briar.android.settings.SettingsModule;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.reporting.DevReportModule;
import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.File;
import java.security.GeneralSecurityException;
@@ -71,20 +60,10 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBL
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = {
SetupModule.class,
DozeHelperModule.class,
ContactExchangeModule.class,
LoginModule.class,
NavDrawerModule.class,
ViewModelModule.class,
SettingsModule.class,
DevReportModule.class,
ContactListModule.class,
// below need to be within same scope as ViewModelProvider.Factory
ForumModule.class,
GroupListModule.class,
GroupConversationModule.class,
SharingModule.class,
ViewModelModule.class
})
public class AppModule {
@@ -196,12 +175,6 @@ public class AppModule {
return devConfig;
}
@Provides
TestAvatarCreator provideTestAvatarCreator(
TestAvatarCreatorImpl testAvatarCreator) {
return testAvatarCreator;
}
@Provides
SharedPreferences provideSharedPreferences(Application app) {
// FIXME unify this with getDefaultSharedPreferences()
@@ -223,10 +196,7 @@ public class AppModule {
ScreenFilterMonitor provideScreenFilterMonitor(
LifecycleManager lifecycleManager,
ScreenFilterMonitorImpl screenFilterMonitor) {
if (SDK_INT <= 29) {
// this keeps track of installed apps and does not work on API 30+
lifecycleManager.registerService(screenFilterMonitor);
}
lifecycleManager.registerService(screenFilterMonitor);
return screenFilterMonitor;
}
@@ -265,17 +235,6 @@ public class AppModule {
@Provides
FeatureFlags provideFeatureFlags() {
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return IS_DEBUG_BUILD;
}
@Override
public boolean shouldEnableProfilePictures() {
return IS_DEBUG_BUILD;
}
};
return () -> IS_DEBUG_BUILD;
}
}

View File

@@ -14,13 +14,19 @@ import android.preference.PreferenceManager;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.google.GoogleEmojiProvider;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
import org.briarproject.bramble.BrambleAppComponent;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.reporting.BriarExceptionHandler;
import org.briarproject.briar.android.reporting.BriarReportPrimer;
import org.briarproject.briar.android.reporting.BriarReportSenderFactory;
import org.briarproject.briar.android.reporting.DevReportActivity;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Collection;
@@ -28,14 +34,50 @@ import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import androidx.annotation.NonNull;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME;
import static org.acra.ReportField.BRAND;
import static org.acra.ReportField.BUILD_CONFIG;
import static org.acra.ReportField.CRASH_CONFIGURATION;
import static org.acra.ReportField.CUSTOM_DATA;
import static org.acra.ReportField.DEVICE_FEATURES;
import static org.acra.ReportField.DISPLAY;
import static org.acra.ReportField.INITIAL_CONFIGURATION;
import static org.acra.ReportField.PACKAGE_NAME;
import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.PRODUCT;
import static org.acra.ReportField.REPORT_ID;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_APP_START_DATE;
import static org.acra.ReportField.USER_CRASH_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@ReportsCrashes(
reportPrimerClass = BriarReportPrimer.class,
logcatArguments = {"-d", "-v", "time", "*:I"},
reportSenderFactoryClasses = {BriarReportSenderFactory.class},
mode = ReportingInteractionMode.DIALOG,
reportDialogClass = DevReportActivity.class,
resDialogOkToast = R.string.dev_report_saved,
deleteOldUnsentReportsOnApplicationStart = false,
buildConfigClass = BuildConfig.class,
customReportContent = {
REPORT_ID,
APP_VERSION_CODE, APP_VERSION_NAME, PACKAGE_NAME,
PHONE_MODEL, ANDROID_VERSION, BRAND, PRODUCT,
BUILD_CONFIG,
CUSTOM_DATA,
STACK_TRACE,
INITIAL_CONFIGURATION, CRASH_CONFIGURATION,
DISPLAY, DEVICE_FEATURES,
USER_APP_START_DATE, USER_CRASH_DATE
}
)
public class BriarApplicationImpl extends Application
implements BriarApplication {
@@ -43,15 +85,12 @@ public class BriarApplicationImpl extends Application
getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler();
private final BriarExceptionHandler exceptionHandler =
new BriarExceptionHandler(this);
private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs;
@Override
protected void attachBaseContext(Context base) {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
if (prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here.
@@ -59,6 +98,7 @@ public class BriarApplicationImpl extends Application
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
setTheme(base, prefs);
ACRA.init(this);
}
@Override
@@ -104,7 +144,7 @@ public class BriarApplicationImpl extends Application
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Localizer.getInstance().setLocale(this);
}

View File

@@ -14,8 +14,6 @@ import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import com.bumptech.glide.Glide;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -248,13 +246,10 @@ public class BriarService extends Service {
LOG.info("Trim memory: near end of LRU list");
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) {
LOG.info("Trim memory: running moderately low");
Glide.get(getApplicationContext()).clearMemory();
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
LOG.info("Trim memory: running low");
// TODO investigate if we can clear Glide cache here as well
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
LOG.warning("Trim memory: running critically low");
// TODO investigate if we can clear Glide cache here as well
// If we're not in the foreground, clear the UI to save memory
if (app.isRunningInBackground()) hideUi();
} else if (LOG.isLoggable(INFO)) {

View File

@@ -58,7 +58,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
/*
* Ignore Play Services if it uses this package name and public key - it's
* Ignore Play Services if it uses this package name and public key - it's
* effectively a system app, but not flagged as such on older systems
*/
private static final String PLAY_SERVICES_PACKAGE =
@@ -108,7 +108,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet());
List<AppDetails> apps = new ArrayList<>();
@SuppressLint("QueryPermissionsNeeded") List<PackageInfo> packageInfos =
List<PackageInfo> packageInfos =
pm.getInstalledPackages(GET_PERMISSIONS);
for (PackageInfo packageInfo : packageInfos) {
if (!allowed.contains(packageInfo.packageName)

View File

@@ -81,7 +81,8 @@ public class AuthorNameFragment extends SetupFragment {
public void onClick(View view) {
Editable text = authorNameInput.getText();
if (text != null) {
viewModel.setAuthorName(text.toString().trim());
setupController.setAuthorName(text.toString().trim());
setupController.showPasswordFragment();
}
}

View File

@@ -104,15 +104,9 @@ public class DozeFragment extends SetupFragment
@Override
public void onClick(View view) {
setNextClicked();
viewModel.dozeExceptionConfirmed();
}
@Override
void setNextClicked() {
super.setNextClicked();
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
}
}

View File

@@ -1,8 +0,0 @@
package org.briarproject.briar.android.account;
import android.content.Context;
interface DozeHelper {
boolean needToShowDozeFragment(Context context);
}

View File

@@ -1,14 +0,0 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import static org.briarproject.briar.android.account.HuaweiView.needsToBeShown;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
class DozeHelperImpl implements DozeHelper {
@Override
public boolean needToShowDozeFragment(Context context) {
return needsDozeWhitelisting(context.getApplicationContext()) ||
needsToBeShown(context.getApplicationContext());
}
}

View File

@@ -1,13 +0,0 @@
package org.briarproject.briar.android.account;
import dagger.Module;
import dagger.Provides;
@Module
public class DozeHelperModule {
@Provides
DozeHelper provideDozeHelper() {
return new DozeHelperImpl();
}
}

View File

@@ -30,6 +30,7 @@ import static org.briarproject.briar.android.util.UiUtils.setError;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SetPasswordFragment extends SetupFragment {
private final static String TAG = SetPasswordFragment.class.getName();
private TextInputLayout passwordEntryWrapper;
@@ -55,7 +56,7 @@ public class SetPasswordFragment extends SetupFragment {
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container,
false);
false);
strengthMeter = v.findViewById(R.id.strength_meter);
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
@@ -70,7 +71,7 @@ public class SetPasswordFragment extends SetupFragment {
passwordConfirmation.addTextChangedListener(this);
nextButton.setOnClickListener(this);
if (!viewModel.needToShowDozeFragment()) {
if (!setupController.needToShowDozeFragment()) {
nextButton.setText(R.string.create_account_button);
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
}
@@ -96,7 +97,7 @@ public class SetPasswordFragment extends SetupFragment {
strengthMeter
.setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE);
float strength = viewModel.estimatePasswordStrength(password1);
float strength = setupController.estimatePasswordStrength(password1);
strengthMeter.setStrength(strength);
boolean strongEnough = strength >= QUITE_WEAK;
@@ -116,20 +117,14 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
setNextClicked();
viewModel.setPassword(passwordEntry.getText().toString());
}
@Override
void setNextClicked() {
super.setNextClicked();
passwordEntry.setFocusable(false);
passwordConfirmation.setFocusable(false);
if (!viewModel.needToShowDozeFragment()) {
setupController.setPassword(passwordEntry.getText().toString());
if (setupController.needToShowDozeFragment()) {
setupController.showDozeFragment();
} else {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
}
}
}

View File

@@ -4,6 +4,7 @@ import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Bundle;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
@@ -14,36 +15,28 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED;
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SetupActivity extends BaseActivity
implements BaseFragmentListener {
private static final String STATE_KEY_AUTHOR_NAME = "authorName";
private static final String STATE_KEY_PASSWORD = "password";
@Inject
ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel;
AccountManager accountManager;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
@Inject
SetupController setupController;
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(SetupViewModel.class);
viewModel.getState().observeEvent(this, this::onStateChanged);
}
@Nullable
private String authorName, password;
@Override
public void onCreate(@Nullable Bundle state) {
@@ -51,27 +44,58 @@ public class SetupActivity extends BaseActivity
// fade-in after splash screen instead of default animation
overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
setContentView(R.layout.activity_fragment_container);
}
private void onStateChanged(SetupViewModel.State state) {
if (state == AUTHOR_NAME) {
if (state == null) {
if (accountManager.accountExists()) throw new AssertionError();
showInitialFragment(AuthorNameFragment.newInstance());
} else if (state == SET_PASSWORD) {
showPasswordFragment();
} else if (state == DOZE) {
showDozeFragment();
} else if (state == CREATED || state == FAILED) {
// TODO: Show an error if failed
showApp();
} else {
authorName = state.getString(STATE_KEY_AUTHOR_NAME);
password = state.getString(STATE_KEY_PASSWORD);
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
setupController.setSetupActivity(this);
}
@Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
if (authorName != null)
state.putString(STATE_KEY_AUTHOR_NAME, authorName);
if (password != null)
state.putString(STATE_KEY_PASSWORD, password);
}
@Nullable
String getAuthorName() {
return authorName;
}
void setAuthorName(String authorName) {
this.authorName = authorName;
}
@Nullable
String getPassword() {
return password;
}
void setPassword(String password) {
this.password = password;
}
void showPasswordFragment() {
if (authorName == null) throw new IllegalStateException();
showNextFragment(SetPasswordFragment.newInstance());
}
@TargetApi(23)
void showDozeFragment() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
showNextFragment(DozeFragment.newInstance());
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.briar.android.account;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface SetupController {
void setSetupActivity(SetupActivity setupActivity);
boolean needToShowDozeFragment();
void setAuthorName(String authorName);
float estimatePasswordStrength(String password);
void setPassword(String password);
/**
* This should be called after the author name has been set.
*/
void showPasswordFragment();
/**
* This should be called after the author name and the password have been
* set.
*/
void showDozeFragment();
/**
* This should be called after the author name and the password have been
* set.
*/
void createAccount();
}

View File

@@ -0,0 +1,115 @@
package org.briarproject.briar.android.account;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
@NotNullByDefault
public class SetupControllerImpl implements SetupController {
private static final Logger LOG =
Logger.getLogger(SetupControllerImpl.class.getName());
private final AccountManager accountManager;
private final PasswordStrengthEstimator strengthEstimator;
@IoExecutor
private final Executor ioExecutor;
@Nullable
private volatile SetupActivity setupActivity;
@Inject
SetupControllerImpl(AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator) {
this.accountManager = accountManager;
this.strengthEstimator = strengthEstimator;
this.ioExecutor = ioExecutor;
}
@Override
public void setSetupActivity(SetupActivity setupActivity) {
this.setupActivity = setupActivity;
}
@Override
public boolean needToShowDozeFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
return DozeView.needsToBeShown(setupActivity) ||
HuaweiView.needsToBeShown(setupActivity);
}
@Override
public void setAuthorName(String authorName) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.setAuthorName(authorName);
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
@Override
public void setPassword(String password) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.setPassword(password);
}
@Override
public void showPasswordFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.showPasswordFragment();
}
@Override
public void showDozeFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.showDozeFragment();
}
@Override
public void createAccount() {
SetupActivity setupActivity = this.setupActivity;
UiResultHandler<Boolean> resultHandler =
new UiResultHandler<Boolean>(setupActivity) {
@Override
public void onResultUi(Boolean result) {
// TODO: Show an error if result is false
if (setupActivity == null)
throw new IllegalStateException();
setupActivity.showApp();
}
};
createAccount(resultHandler);
}
// Package access for testing
void createAccount(ResultHandler<Boolean> resultHandler) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
String authorName = setupActivity.getAuthorName();
if (authorName == null) throw new IllegalStateException();
String password = setupActivity.getPassword();
if (password == null) throw new IllegalStateException();
ioExecutor.execute(() -> {
LOG.info("Creating account");
resultHandler.onResult(accountManager.createAccount(authorName,
password));
});
}
}

View File

@@ -1,13 +1,11 @@
package org.briarproject.briar.android.account;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -19,10 +17,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
@@ -34,40 +29,8 @@ import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener {
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
private boolean clicked = false;
@Inject
ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(requireActivity())
.get(SetupViewModel.class);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
}
if (clicked) {
setNextClicked();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_KEY_CLICKED, clicked);
}
@CallSuper
void setNextClicked() {
this.clicked = true;
}
SetupController setupController;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

View File

@@ -1,18 +0,0 @@
package org.briarproject.briar.android.account;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class SetupModule {
@Binds
@IntoMap
@ViewModelKey(SetupViewModel.class)
abstract ViewModel bindSetupViewModel(
SetupViewModel setupViewModel);
}

View File

@@ -1,110 +0,0 @@
package org.briarproject.briar.android.account;
import android.app.Application;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED;
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class SetupViewModel extends AndroidViewModel {
enum State {AUTHOR_NAME, SET_PASSWORD, DOZE, CREATED, FAILED}
private static final Logger LOG =
getLogger(SetupActivity.class.getName());
@Nullable
private String authorName, password;
private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
private final AccountManager accountManager;
private final Executor ioExecutor;
private final PasswordStrengthEstimator strengthEstimator;
private final DozeHelper dozeHelper;
@Inject
SetupViewModel(Application app,
AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator,
DozeHelper dozeHelper) {
super(app);
this.accountManager = accountManager;
this.ioExecutor = ioExecutor;
this.strengthEstimator = strengthEstimator;
this.dozeHelper = dozeHelper;
ioExecutor.execute(() -> {
if (accountManager.accountExists()) {
throw new AssertionError();
} else {
state.postEvent(AUTHOR_NAME);
}
});
}
LiveEvent<State> getState() {
return state;
}
void setAuthorName(String authorName) {
this.authorName = authorName;
state.setEvent(SET_PASSWORD);
}
void setPassword(String password) {
if (authorName == null) throw new IllegalStateException();
this.password = password;
if (needToShowDozeFragment()) {
state.setEvent(DOZE);
} else {
createAccount();
}
}
float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
boolean needToShowDozeFragment() {
return dozeHelper.needToShowDozeFragment(getApplication());
}
void dozeExceptionConfirmed() {
createAccount();
}
private void createAccount() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
ioExecutor.execute(() -> {
if (accountManager.createAccount(authorName, password)) {
LOG.info("Created account");
state.postEvent(CREATED);
} else {
LOG.warning("Failed to create account");
state.postEvent(FAILED);
}
});
}
}

View File

@@ -21,6 +21,7 @@ import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
@@ -32,6 +33,7 @@ import org.briarproject.briar.android.conversation.ImageFragment;
import org.briarproject.briar.android.forum.CreateForumActivity;
import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.forum.ForumListFragment;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.introduction.ContactChooserFragment;
import org.briarproject.briar.android.introduction.IntroductionActivity;
@@ -49,6 +51,7 @@ import org.briarproject.briar.android.navdrawer.TransportsActivity;
import org.briarproject.briar.android.panic.PanicPreferencesActivity;
import org.briarproject.briar.android.panic.PanicResponderActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
import org.briarproject.briar.android.privategroup.creation.CreateGroupModule;
@@ -57,15 +60,12 @@ import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationModule;
import org.briarproject.briar.android.privategroup.list.GroupListFragment;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity;
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment;
import org.briarproject.briar.android.reporting.CrashFragment;
import org.briarproject.briar.android.reporting.CrashReportActivity;
import org.briarproject.briar.android.reporting.ReportFormFragment;
import org.briarproject.briar.android.settings.ConfirmAvatarDialogFragment;
import org.briarproject.briar.android.settings.SettingsActivity;
import org.briarproject.briar.android.settings.SettingsFragment;
import org.briarproject.briar.android.sharing.BlogInvitationActivity;
@@ -86,11 +86,15 @@ import dagger.Component;
@Component(modules = {
ActivityModule.class,
BlogModule.class,
ContactModule.class,
CreateGroupModule.class,
ForumModule.class,
GroupInvitationModule.class,
GroupConversationModule.class,
GroupListModule.class,
GroupMemberModule.class,
GroupRevealModule.class,
SharingModule.SharingLegacyModule.class
SharingModule.class
}, dependencies = AndroidComponent.class)
public interface ActivityComponent {
@@ -180,8 +184,6 @@ public interface ActivityComponent {
void inject(PendingContactListActivity activity);
void inject(CrashReportActivity crashReportActivity);
// Fragments
void inject(AuthorNameFragment fragment);
@@ -232,10 +234,4 @@ public interface ActivityComponent {
void inject(ImageFragment imageFragment);
void inject(ReportFormFragment reportFormFragment);
void inject(CrashFragment crashFragment);
void inject(ConfirmAvatarDialogFragment fragment);
}

View File

@@ -2,6 +2,8 @@ package org.briarproject.briar.android.activity;
import android.app.Activity;
import org.briarproject.briar.android.account.SetupController;
import org.briarproject.briar.android.account.SetupControllerImpl;
import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl;
import org.briarproject.briar.android.controller.DbController;
@@ -33,6 +35,13 @@ public class ActivityModule {
return activity;
}
@ActivityScope
@Provides
SetupController provideSetupController(
SetupControllerImpl setupController) {
return setupController;
}
@ActivityScope
@Provides
protected BriarController provideBriarController(

View File

@@ -6,6 +6,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
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;
@@ -14,8 +15,10 @@ import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.Localizer;
import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.reporting.DevReportActivity;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
@@ -37,11 +40,9 @@ import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static androidx.lifecycle.Lifecycle.State.STARTED;
import static java.util.Collections.emptyList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
@@ -49,6 +50,7 @@ import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
/**
* Warning: Some activities don't extend {@link BaseActivity}.
* E.g. {@link DevReportActivity}
*/
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -86,6 +88,7 @@ public abstract class BaseActivity extends AppCompatActivity
activityComponent = DaggerActivityComponent.builder()
.androidComponent(applicationComponent)
.activityModule(getActivityModule())
.forumModule(getForumModule())
.build();
injectActivity(activityComponent);
super.onCreate(state);
@@ -120,6 +123,10 @@ public abstract class BaseActivity extends AppCompatActivity
return new ActivityModule(this);
}
protected ForumModule getForumModule() {
return new ForumModule();
}
@Override
protected void onStart() {
super.onStart();
@@ -195,15 +202,9 @@ public abstract class BaseActivity extends AppCompatActivity
// If the dialog is already visible, filter the tap
ScreenFilterDialogFragment f = findDialogFragment();
if (f != null && f.isVisible()) return false;
Collection<AppDetails> apps;
// querying all apps is only possible at API 29 and below
if (SDK_INT <= 29) {
apps = screenFilterMonitor.getApps();
// If all overlay apps have been allowed, allow the tap
if (apps.isEmpty()) return true;
} else {
apps = emptyList();
}
Collection<AppDetails> apps = screenFilterMonitor.getApps();
// If all overlay apps have been allowed, allow the tap
if (apps.isEmpty()) return true;
// Show dialog unless onSaveInstanceState() has been called, see #1112
FragmentManager fm = getSupportFragmentManager();
if (!fm.isStateSaved()) {
@@ -240,7 +241,7 @@ public abstract class BaseActivity extends AppCompatActivity
}
@UiThread
public void handleException(Exception e) {
public void handleDbException(DbException e) {
supportFinishAfterTransition();
}
@@ -265,12 +266,7 @@ public abstract class BaseActivity extends AppCompatActivity
private void protectToolbar() {
findToolbar();
if (toolbar != null) {
boolean filter;
if (SDK_INT <= 29) {
filter = !screenFilterMonitor.getApps().isEmpty();
} else {
filter = true;
}
boolean filter = !screenFilterMonitor.getApps().isEmpty();
UiUtils.setFilterTouchesWhenObscured(toolbar, filter);
}
}

View File

@@ -129,6 +129,10 @@ public abstract class BriarActivity extends BaseActivity {
lockManager.onActivityStop();
}
protected boolean signedIn() {
return briarController.accountSignedIn();
}
/**
* Sets the transition animations.
*
@@ -160,6 +164,7 @@ public abstract class BriarActivity extends BaseActivity {
* @param ownLayout true if the custom toolbar brings its own layout
* @return the Toolbar object or null if content view did not contain one
*/
@Nullable
protected Toolbar setUpCustomToolbar(boolean ownLayout) {
// Custom Toolbar
Toolbar toolbar = findViewById(R.id.toolbar);

View File

@@ -16,6 +16,5 @@ public interface RequestCodes {
int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13;
int REQUEST_SAVE_ATTACHMENT = 14;
int REQUEST_AVATAR_IMAGE = 15;
}

View File

@@ -1,24 +1,33 @@
package org.briarproject.briar.android.attachment;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
import android.net.Uri;
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.GroupId;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.logging.Logger;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import static android.graphics.Bitmap.CompressFormat.JPEG;
import static android.graphics.BitmapFactory.decodeStream;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
@@ -26,16 +35,19 @@ 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;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault
class AttachmentCreationTask {
private static final Logger LOG =
private static Logger LOG =
getLogger(AttachmentCreationTask.class.getName());
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
private final MessagingManager messagingManager;
private final ContentResolver contentResolver;
private final ImageCompressor imageCompressor;
private final ImageSizeCalculator imageSizeCalculator;
private final GroupId groupId;
private final Collection<Uri> uris;
private final boolean needsSize;
@@ -47,11 +59,11 @@ class AttachmentCreationTask {
AttachmentCreationTask(MessagingManager messagingManager,
ContentResolver contentResolver,
AttachmentCreator attachmentCreator,
ImageCompressor imageCompressor,
ImageSizeCalculator imageSizeCalculator,
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
this.messagingManager = messagingManager;
this.contentResolver = contentResolver;
this.imageCompressor = imageCompressor;
this.imageSizeCalculator = imageSizeCalculator;
this.groupId = groupId;
this.uris = uris;
this.needsSize = needsSize;
@@ -98,19 +110,66 @@ class AttachmentCreationTask {
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
if (!asList(getSupportedImageContentTypes()).contains(contentType)) {
throw new UnsupportedMimeTypeException(contentType, uri);
String uriString = uri.toString();
throw new UnsupportedMimeTypeException("", contentType, uriString);
}
InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
is = imageCompressor
.compressImage(is, contentType);
is = compressImage(is, contentType);
contentType = "image/jpeg";
long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp,
ImageCompressor.MIME_TYPE, is);
.addLocalAttachment(groupId, timestamp, contentType, is);
tryToClose(is, LOG, WARNING);
logDuration(LOG, "Storing attachment", start);
return h;
}
@VisibleForTesting
InputStream compressImage(InputStream is, String contentType)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
Bitmap bitmap = createBitmap(is, contentType);
for (int quality = 100; quality >= 0; quality -= 10) {
if (!bitmap.compress(JPEG, quality, out))
throw new IOException();
if (out.size() <= MAX_IMAGE_SIZE) {
if (LOG.isLoggable(INFO)) {
LOG.info("Compressed image to "
+ out.size() + " bytes, quality " + quality);
}
return new ByteArrayInputStream(out.toByteArray());
}
out.reset();
}
throw new IOException();
} finally {
tryToClose(is, LOG, WARNING);
}
}
private Bitmap createBitmap(InputStream is, String contentType)
throws IOException {
is = new BufferedInputStream(is);
Size size = imageSizeCalculator.getSize(is, contentType);
if (size.error) throw new IOException();
if (LOG.isLoggable(INFO))
LOG.info("Original image size: " + size.width + "x" + size.height);
int dimension = Math.max(size.width, size.height);
int inSampleSize = 1;
while (dimension > MAX_ATTACHMENT_DIMENSION) {
inSampleSize *= 2;
dimension /= 2;
}
if (LOG.isLoggable(INFO))
LOG.info("Scaling attachment by factor of " + inSampleSize);
Options options = new Options();
options.inSampleSize = inSampleSize;
if (contentType.equals("image/png"))
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = decodeStream(is, null, options);
if (bitmap == null) throw new IOException();
return bitmap;
}
}

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.Collection;
import java.util.List;

View File

@@ -10,11 +10,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.FileTooBigException;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException;
import java.util.ArrayList;
@@ -36,12 +36,12 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault
class AttachmentCreatorImpl implements AttachmentCreator {
private static final Logger LOG =
private static Logger LOG =
getLogger(AttachmentCreatorImpl.class.getName());
private final Application app;
@@ -49,7 +49,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
private final Executor ioExecutor;
private final MessagingManager messagingManager;
private final AttachmentRetriever retriever;
private final ImageCompressor imageCompressor;
private final ImageSizeCalculator imageSizeCalculator;
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
@@ -64,12 +64,12 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@Inject
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
MessagingManager messagingManager, AttachmentRetriever retriever,
ImageCompressor imageCompressor) {
ImageSizeCalculator imageSizeCalculator) {
this.app = app;
this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager;
this.retriever = retriever;
this.imageCompressor = imageCompressor;
this.imageSizeCalculator = imageSizeCalculator;
}
@Override
@@ -89,7 +89,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
if (id == null) throw new IllegalStateException();
boolean needsSize = uris.size() == 1;
task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, imageCompressor, id,
app.getContentResolver(), this, imageSizeCalculator, id,
uris, needsSize);
ioExecutor.execute(() -> task.storeAttachments());
});

View File

@@ -4,9 +4,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import javax.annotation.concurrent.Immutable;
@@ -79,9 +78,6 @@ public class AttachmentItem implements Parcelable {
}
protected AttachmentItem(Parcel in) {
byte[] groupIdByte = new byte[GroupId.LENGTH];
in.readByteArray(groupIdByte);
GroupId groupId = new GroupId(groupIdByte);
byte[] messageIdByte = new byte[MessageId.LENGTH];
in.readByteArray(messageIdByte);
MessageId messageId = new MessageId(messageIdByte);
@@ -92,7 +88,7 @@ public class AttachmentItem implements Parcelable {
thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt();
state = State.valueOf(requireNonNull(in.readString()));
header = new AttachmentHeader(groupId, messageId, mimeType);
header = new AttachmentHeader(messageId, mimeType);
}
public AttachmentHeader getHeader() {
@@ -146,7 +142,6 @@ public class AttachmentItem implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(header.getGroupId().getBytes());
dest.writeByteArray(header.getMessageId().getBytes());
dest.writeInt(width);
dest.writeInt(height);

View File

@@ -3,7 +3,7 @@ package org.briarproject.briar.android.attachment;
import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.Collection;
import java.util.List;

View File

@@ -12,6 +12,16 @@ import static org.briarproject.briar.android.attachment.AttachmentDimensions.get
@Module
public class AttachmentModule {
@Provides
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
return imageHelper;
}
@Provides
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
return new ImageSizeCalculator(imageHelper);
}
@Provides
AttachmentDimensions provideAttachmentDimensions(Application app) {
return getAttachmentDimensions(app.getResources());

View File

@@ -4,8 +4,8 @@ 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.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
@@ -49,10 +49,10 @@ public interface AttachmentRetriever {
* Loads an {@link AttachmentItem}
* that arrived via an {@link AttachmentReceivedEvent}
* and notifies the associated {@link LiveData}.
* <p>
*
* Note that you need to call {@link #getAttachmentItems(PrivateMessageHeader)}
* first to get the LiveData.
* <p>
*
* It is possible that no LiveData is available,
* because the message of the AttachmentItem did not arrive, yet.
* In this case, the load wil be deferred until the message arrives.

View File

@@ -6,12 +6,9 @@ import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem.State;
import org.briarproject.briar.android.attachment.media.ImageHelper;
import org.briarproject.briar.android.attachment.media.ImageSizeCalculator;
import org.briarproject.briar.android.attachment.media.Size;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentReader;
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.PrivateMessageHeader;
import java.io.BufferedInputStream;
@@ -46,7 +43,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor
private final Executor dbExecutor;
private final AttachmentReader attachmentReader;
private final MessagingManager messagingManager;
private final ImageHelper imageHelper;
private final ImageSizeCalculator imageSizeCalculator;
private final int defaultSize;
@@ -60,10 +57,11 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@Inject
AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor,
AttachmentReader attachmentReader, AttachmentDimensions dimensions,
ImageHelper imageHelper, ImageSizeCalculator imageSizeCalculator) {
MessagingManager messagingManager,
AttachmentDimensions dimensions, ImageHelper imageHelper,
ImageSizeCalculator imageSizeCalculator) {
this.dbExecutor = dbExecutor;
this.attachmentReader = attachmentReader;
this.messagingManager = messagingManager;
this.imageHelper = imageHelper;
this.imageSizeCalculator = imageSizeCalculator;
defaultSize = dimensions.defaultSize;
@@ -77,7 +75,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor
public Attachment getMessageAttachment(AttachmentHeader h)
throws DbException {
return attachmentReader.getAttachment(h);
return messagingManager.getAttachment(h);
}
@Override
@@ -88,11 +86,13 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
boolean needsSize = headers.size() == 1;
for (AttachmentHeader h : headers) {
// try cache for existing item live data
MutableLiveData<AttachmentItem> liveData =
itemsWithSize.get(h.getMessageId());
if (!needsSize && liveData == null) {
// check cache for items that don't need the size
liveData = itemsWithoutSize.get(h.getMessageId());
MutableLiveData<AttachmentItem> liveData;
if (needsSize) liveData = itemsWithSize.get(h.getMessageId());
else {
// try items with size first, as they work as well
liveData = itemsWithSize.get(h.getMessageId());
if (liveData == null)
liveData = itemsWithoutSize.get(h.getMessageId());
}
// create new live data with LOADING item if cache miss
@@ -131,7 +131,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
// If a live data is already cached we don't need to do anything
if (itemsWithSize.containsKey(h.getMessageId())) return;
try {
Attachment a = attachmentReader.getAttachment(h);
Attachment a = messagingManager.getAttachment(h);
AttachmentItem item = createAttachmentItem(a, true);
MutableLiveData<AttachmentItem> liveData =
new MutableLiveData<>(item);
@@ -173,7 +173,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
Attachment a;
AttachmentItem item;
try {
a = attachmentReader.getAttachment(h);
a = messagingManager.getAttachment(h);
item = createAttachmentItem(a, needsSize);
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
@@ -210,30 +210,26 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) {
// calculate thumbnail size
Size thumbnailSize =
new Size(defaultSize, defaultSize, size.getMimeType());
if (!size.hasError()) {
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
if (!size.error) {
thumbnailSize =
getThumbnailSize(size.getWidth(), size.getHeight(),
size.getMimeType());
getThumbnailSize(size.width, size.height, size.mimeType);
}
// get file extension
String extension =
imageHelper.getExtensionFromMimeType(size.getMimeType());
boolean hasError = extension == null || size.hasError();
if (!h.getContentType().equals(size.getMimeType())) {
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
boolean hasError = extension == null || size.error;
if (!h.getContentType().equals(size.mimeType)) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Header has different mime type (" +
h.getContentType() + ") than image (" +
size.getMimeType() + ").");
h.getContentType() + ") than image (" + size.mimeType +
").");
}
hasError = true;
}
if (extension == null) extension = "";
State state = hasError ? ERROR : AVAILABLE;
return new AttachmentItem(h, size.getWidth(), size.getHeight(),
extension, thumbnailSize.getWidth(), thumbnailSize.getHeight(),
state);
return new AttachmentItem(h, size.width, size.height,
extension, thumbnailSize.width, thumbnailSize.height, state);
}
private Size getThumbnailSize(int width, int height, String mimeType) {

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment.media;
package org.briarproject.briar.android.attachment;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment.media;
package org.briarproject.briar.android.attachment;
import android.graphics.BitmapFactory;
import android.webkit.MimeTypeMap;

View File

@@ -1,9 +1,9 @@
package org.briarproject.briar.android.attachment.media;
package org.briarproject.briar.android.attachment;
import com.bumptech.glide.util.MarkEnforcingInputStream;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.attachment.media.ImageHelper.DecodeResult;
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
import java.io.IOException;
import java.io.InputStream;
@@ -23,21 +23,20 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class ImageSizeCalculatorImpl implements ImageSizeCalculator {
class ImageSizeCalculator {
private static final Logger LOG =
getLogger(ImageSizeCalculatorImpl.class.getName());
getLogger(ImageSizeCalculator.class.getName());
private static final int READ_LIMIT = 1024 * 8192;
private final ImageHelper imageHelper;
ImageSizeCalculatorImpl(ImageHelper imageHelper) {
ImageSizeCalculator(ImageHelper imageHelper) {
this.imageHelper = imageHelper;
}
@Override
public Size getSize(InputStream is, String contentType) {
Size getSize(InputStream is, String contentType) {
Size size = new Size();
is = new MarkEnforcingInputStream(is);
is.mark(READ_LIMIT);
@@ -50,7 +49,7 @@ class ImageSizeCalculatorImpl implements ImageSizeCalculator {
logException(LOG, WARNING, e);
}
}
if (size.hasError()) {
if (size.error) {
// need to mark again to re-add read limit
is.mark(READ_LIMIT);
try {

View File

@@ -0,0 +1,23 @@
package org.briarproject.briar.android.attachment;
class Size {
final int width;
final int height;
final String mimeType;
final boolean error;
Size(int width, int height, String mimeType) {
this.width = width;
this.height = height;
this.mimeType = mimeType;
this.error = false;
}
Size() {
this.width = 0;
this.height = 0;
this.mimeType = "";
this.error = true;
}
}

View File

@@ -1,24 +0,0 @@
package org.briarproject.briar.android.attachment;
import android.net.Uri;
import java.io.IOException;
public class UnsupportedMimeTypeException extends IOException {
private final String mimeType;
private final Uri uri;
public UnsupportedMimeTypeException(String mimeType, Uri uri) {
this.mimeType = mimeType;
this.uri = uri;
}
public String getMimeType() {
return mimeType;
}
public Uri getUri() {
return uri;
}
}

View File

@@ -1,39 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import android.graphics.Bitmap;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
public interface ImageCompressor {
/**
* The MIME type of compressed images
*/
String MIME_TYPE = "image/jpeg";
/**
* Load an image from {@code is}, compress it and return an InputStream
* from which the resulting image can be read. The image will be compressed
* as a JPEG image such that it fits into a message.
*
* @param is the stream to read the source image from
* @param contentType the mimetype of the source image such as "image/jpeg"
* as obtained by {@link android.content.ContentResolver#getType(Uri)}
* @return a stream from which the resulting image can be read
*/
InputStream compressImage(InputStream is, String contentType)
throws IOException;
/**
* Compress an image and return an InputStream from which the resulting
* image can be read. The image will be compressed as a JPEG image such that
* it fits into a message.
*
* @param bitmap the source image
* @return a stream from which the resulting image can be read
*/
InputStream compressImage(Bitmap bitmap) throws IOException;
}

View File

@@ -1,92 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.graphics.Bitmap.CompressFormat.JPEG;
import static android.graphics.BitmapFactory.decodeStream;
import static java.util.logging.Level.INFO;
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.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE;
class ImageCompressorImpl implements ImageCompressor {
private static final Logger LOG =
getLogger(ImageCompressorImpl.class.getName());
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
private final ImageSizeCalculator imageSizeCalculator;
@Inject
ImageCompressorImpl(ImageSizeCalculator imageSizeCalculator) {
this.imageSizeCalculator = imageSizeCalculator;
}
@Override
public InputStream compressImage(InputStream is, String contentType)
throws IOException {
try {
Bitmap bitmap =
createBitmap(is, contentType, MAX_ATTACHMENT_DIMENSION);
return compressImage(bitmap);
} finally {
tryToClose(is, LOG, WARNING);
}
}
@Override
public InputStream compressImage(Bitmap bitmap) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int quality = 100; quality >= 0; quality -= 10) {
if (!bitmap.compress(JPEG, quality, out))
throw new IOException();
if (out.size() <= MAX_IMAGE_SIZE) {
if (LOG.isLoggable(INFO)) {
LOG.info("Compressed image to "
+ out.size() + " bytes, quality " + quality);
}
return new ByteArrayInputStream(out.toByteArray());
}
out.reset();
}
throw new IOException();
}
private Bitmap createBitmap(InputStream is, String contentType, int maxSize)
throws IOException {
is = new BufferedInputStream(is);
Size size = imageSizeCalculator.getSize(is, contentType);
if (size.hasError()) throw new IOException();
if (LOG.isLoggable(INFO))
LOG.info("Original image size: " + size.getWidth() + "x" +
size.getHeight());
int dimension = Math.max(size.getWidth(), size.getHeight());
int inSampleSize = 1;
while (dimension > maxSize) {
inSampleSize *= 2;
dimension /= 2;
}
if (LOG.isLoggable(INFO))
LOG.info("Scaling attachment by factor of " + inSampleSize);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
if (contentType.equals("image/png"))
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = decodeStream(is, null, options);
if (bitmap == null) throw new IOException();
return bitmap;
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream;
@NotNullByDefault
public interface ImageSizeCalculator {
/**
* Determine the size of the image that can be read from {@code is}.
*
* @param contentType the mime type of the image. If "image/jpeg" is passed,
* the implementation will try to determine the size from the Exif header
*/
Size getSize(InputStream is, String contentType);
}

View File

@@ -1,24 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import dagger.Module;
import dagger.Provides;
@Module
public class MediaModule {
@Provides
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
return imageHelper;
}
@Provides
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
return new ImageSizeCalculatorImpl(imageHelper);
}
@Provides
ImageCompressor provideImageCompressor(
ImageCompressorImpl imageCompressor) {
return imageCompressor;
}
}

View File

@@ -1,46 +0,0 @@
package org.briarproject.briar.android.attachment.media;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class Size {
private final int width;
private final int height;
private final String mimeType;
private final boolean error;
public Size(int width, int height, String mimeType) {
this.width = width;
this.height = height;
this.mimeType = mimeType;
this.error = false;
}
public Size() {
this.width = 0;
this.height = 0;
this.mimeType = "";
this.error = true;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getMimeType() {
return mimeType;
}
public boolean hasError() {
return error;
}
}

View File

@@ -232,7 +232,7 @@ public class BlogFragment extends BaseFragment
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
}
);
@@ -277,7 +277,7 @@ public class BlogFragment extends BaseFragment
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
}
@@ -296,7 +296,7 @@ public class BlogFragment extends BaseFragment
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
}
@@ -318,7 +318,7 @@ public class BlogFragment extends BaseFragment
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
}
@@ -398,7 +398,7 @@ public class BlogFragment extends BaseFragment
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
}

View File

@@ -58,7 +58,7 @@ public class BlogPostFragment extends BasePostFragment implements BlogListener {
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.blog.BlogPostHeader;

View File

@@ -156,7 +156,7 @@ public class FeedFragment extends BaseFragment implements
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
}
@@ -187,7 +187,7 @@ public class FeedFragment extends BaseFragment implements
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
}
@@ -242,7 +242,7 @@ public class FeedFragment extends BaseFragment implements
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
}
);

View File

@@ -79,7 +79,7 @@ public class FeedPostFragment extends BasePostFragment {
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
}

View File

@@ -20,7 +20,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.List;
@@ -101,7 +101,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
@@ -128,7 +128,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
new UiExceptionHandler<DbException>(this) {
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
handleDbException(exception);
}
});
finish();
@@ -156,16 +156,16 @@ public class ReblogFragment extends BaseFragment implements SendListener {
progressBar = v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
}, getFragmentManager());
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
}, getFragmentManager());
input = v.findViewById(R.id.inputText);
}
}

View File

@@ -19,10 +19,10 @@ import org.briarproject.briar.android.view.TextInputView;
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.attachment.AttachmentHeader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.security.GeneralSecurityException;
import java.util.List;

View File

@@ -3,12 +3,14 @@ package org.briarproject.briar.android.contact;
import android.content.Context;
import android.view.View;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.briar.android.util.BriarAdapter;
import javax.annotation.Nullable;
import androidx.annotation.NonNull;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
public abstract class BaseContactListAdapter<I extends ContactItem, VH extends ContactItemViewHolder<I>>
@@ -45,6 +47,15 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
return true;
}
int findItemPosition(ContactId c) {
for (int i = 0; i < getItemCount(); i++) {
I item = getItemAt(i);
if (item != null && item.getContact().getId().equals(c))
return i;
}
return INVALID_POSITION; // Not found
}
public interface OnContactClickListener<I> {
void onItemClick(View view, I item);
}

View File

@@ -2,26 +2,22 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
@Immutable
@NotThreadSafe
@NotNullByDefault
public class ContactItem {
private final Contact contact;
private final AuthorInfo authorInfo;
private final boolean connected;
private boolean connected;
public ContactItem(Contact contact, AuthorInfo authorInfo) {
this(contact, authorInfo, false);
public ContactItem(Contact contact) {
this(contact, false);
}
public ContactItem(Contact contact, AuthorInfo authorInfo,
boolean connected) {
public ContactItem(Contact contact, boolean connected) {
this.contact = contact;
this.authorInfo = authorInfo;
this.connected = connected;
}
@@ -29,12 +25,12 @@ public class ContactItem {
return contact;
}
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
boolean isConnected() {
return connected;
}
void setConnected(boolean connected) {
this.connected = connected;
}
}

View File

@@ -5,6 +5,7 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -13,9 +14,9 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
import im.delight.android.identicons.IdenticonDrawable;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
@UiThread
@NotNullByDefault
@@ -39,7 +40,9 @@ public class ContactItemViewHolder<I extends ContactItem>
}
protected void bind(I item, @Nullable OnContactClickListener<I> listener) {
setAvatar(avatar, item);
Author author = item.getContact().getAuthor();
avatar.setImageDrawable(
new IdenticonDrawable(author.getId().getBytes()));
name.setText(getContactDisplayName(item.getContact()));
if (bulb != null) {

View File

@@ -1,73 +1,50 @@
package org.briarproject.briar.android.contact;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.ListAdapter;
@NotNullByDefault
public class ContactListAdapter extends
ListAdapter<ContactListItem, ContactListItemViewHolder> {
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
// TODO: using the click listener interface from BaseContactListAdapter on
// purpose here because it is entangled with ContactListItemViewHolder. At
// some point we probably want to change that.
protected final OnContactClickListener<ContactListItem> listener;
public ContactListAdapter(
public ContactListAdapter(Context context,
OnContactClickListener<ContactListItem> listener) {
super(new ContactListCallback());
this.listener = listener;
}
@NotNullByDefault
private static class ContactListCallback
extends ItemCallback<ContactListItem> {
@Override
public boolean areItemsTheSame(ContactListItem c1, ContactListItem c2) {
return c1.getContact().equals(c2.getContact());
}
@Override
public boolean areContentsTheSame(ContactListItem c1,
ContactListItem c2) {
// check for all properties that influence visual
// representation of contact
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false;
}
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
if (c1.isConnected() != c2.isConnected()) {
return false;
}
return NullSafety.equals(c1.getAuthorInfo().getAvatarHeader(),
c2.getAuthorInfo().getAvatarHeader());
}
super(context, ContactListItem.class, listener);
}
@Override
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
int viewType) {
int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_contact, viewGroup, false);
return new ContactListItemViewHolder(v);
}
@Override
public void onBindViewHolder(ContactListItemViewHolder viewHolder,
int position) {
viewHolder.bind(getItem(position), listener);
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
// check for all properties that influence visual
// representation of contact
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false;
}
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
return c1.isConnected() == c2.isConnected();
}
@Override
public int compare(ContactListItem c1, ContactListItem c2) {
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
}
}

View File

@@ -10,9 +10,23 @@ import android.widget.TextView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -23,35 +37,55 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.UiThread;
import androidx.lifecycle.ViewModelProvider;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static androidx.core.view.ViewCompat.getTransitionName;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ContactListFragment extends BaseFragment
implements OnMenuItemClickListener,
OnContactClickListener<ContactListItem> {
public class ContactListFragment extends BaseFragment implements EventListener,
OnMenuItemClickListener {
public static final String TAG = ContactListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Inject
ViewModelProvider.Factory viewModelFactory;
ConnectionRegistry connectionRegistry;
@Inject
EventBus eventBus;
@Inject
AndroidNotificationManager notificationManager;
private ContactListViewModel viewModel;
private final ContactListAdapter adapter = new ContactListAdapter(this);
private ContactListAdapter adapter;
private BriarRecyclerView list;
/**
* The Snackbar is non-null when shown and null otherwise.
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
@@ -59,6 +93,12 @@ public class ContactListFragment extends BaseFragment
@Nullable
private Snackbar snackbar = null;
// Fields that are accessed from background threads must be volatile
@Inject
volatile ContactManager contactManager;
@Inject
volatile ConversationManager conversationManager;
public static ContactListFragment newInstance() {
Bundle args = new Bundle();
ContactListFragment fragment = new ContactListFragment();
@@ -74,8 +114,6 @@ public class ContactListFragment extends BaseFragment
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ContactListViewModel.class);
}
@Nullable
@@ -91,6 +129,37 @@ public class ContactListFragment extends BaseFragment
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
speedDial.addOnMenuItemClickListener(this);
OnContactClickListener<ContactListItem> onContactClickListener =
(view, item) -> {
Intent i = new Intent(getActivity(),
ConversationActivity.class);
ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt());
if (SDK_INT >= 23 && !isSamsung7()) {
ContactListItemViewHolder holder =
(ContactListItemViewHolder) list
.getRecyclerView()
.findViewHolderForAdapterPosition(
adapter.findItemPosition(item));
Pair<View, String> avatar =
Pair.create(holder.avatar,
getTransitionName(holder.avatar));
Pair<View, String> bulb =
Pair.create(holder.bulb,
getTransitionName(holder.bulb));
ActivityOptionsCompat options =
makeSceneTransitionAnimation(getActivity(),
avatar, bulb);
ActivityCompat.startActivity(getActivity(), i,
options.toBundle());
} else {
// work-around for android bug #224270
startActivity(i);
}
};
adapter = new ContactListAdapter(requireContext(),
onContactClickListener);
list = contentView.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(requireContext()));
list.setAdapter(adapter);
@@ -98,30 +167,9 @@ public class ContactListFragment extends BaseFragment
list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyAction(getString(R.string.no_contacts_action));
viewModel.getContactListItems()
.observe(getViewLifecycleOwner(), result -> {
result.onError(this::handleException).onSuccess(items -> {
adapter.submitList(items);
if (requireNonNull(items).size() == 0) list.showData();
});
});
viewModel.getHasPendingContacts()
.observe(getViewLifecycleOwner(), hasPending -> {
if (hasPending) showSnackBar();
else dismissSnackBar();
});
return contentView;
}
@Override
public void onItemClick(View view, ContactListItem item) {
Intent i = new Intent(getActivity(), ConversationActivity.class);
ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt());
startActivity(i);
}
@Override
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
int itemId) {
@@ -140,20 +188,131 @@ public class ContactListFragment extends BaseFragment
@Override
public void onStart() {
super.onStart();
viewModel.clearAllContactNotifications();
viewModel.clearAllContactAddedNotifications();
viewModel.loadContacts();
viewModel.checkForPendingContacts();
eventBus.addListener(this);
notificationManager.clearAllContactNotifications();
notificationManager.clearAllContactAddedNotifications();
loadContacts();
checkForPendingContacts();
list.startPeriodicUpdate();
}
private void checkForPendingContacts() {
listener.runOnDbThread(() -> {
try {
if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(this::dismissSnackBar);
} else {
runOnUiThreadUnlessDestroyed(this::showSnackBar);
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override
public void onStop() {
super.onStop();
eventBus.removeListener(this);
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate();
dismissSnackBar();
}
private void loadContacts() {
int revision = adapter.getRevision();
listener.runOnDbThread(() -> {
try {
long start = now();
List<ContactListItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts()) {
try {
ContactId id = c.getId();
GroupCount count =
conversationManager.getGroupCount(id);
boolean connected =
connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count));
} catch (NoSuchContactException e) {
// Continue
}
}
logDuration(LOG, "Full load", start);
displayContacts(revision, contacts);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayContacts(int revision, List<ContactListItem> contacts) {
runOnUiThreadUnlessDestroyed(() -> {
if (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (contacts.isEmpty()) list.showData();
else adapter.replaceAll(contacts);
} else {
LOG.info("Concurrent update, reloading");
loadContacts();
}
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading");
loadContacts();
} else if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId(), true);
} else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId(), false);
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof ConversationMessageReceivedEvent) {
LOG.info("Conversation message received, updating item");
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h);
} else if (e instanceof PendingContactAddedEvent ||
e instanceof PendingContactRemovedEvent) {
checkForPendingContacts();
}
}
@UiThread
private void updateItem(ContactId c, ConversationMessageHeader h) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) {
item.addMessage(h);
adapter.updateItemAt(position, item);
}
}
@UiThread
private void removeItem(ContactId c) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) adapter.remove(item);
}
@UiThread
private void setConnected(ContactId c, boolean connected) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) {
item.setConnected(connected);
adapter.updateItemAt(position, item);
}
}
@UiThread
private void showSnackBar() {
if (snackbar != null) return;
@@ -176,4 +335,5 @@ public class ContactListFragment extends BaseFragment
Intent i = new Intent(getContext(), PendingContactListActivity.class);
startActivity(i);
}
}

View File

@@ -2,58 +2,31 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
@Immutable
@NotThreadSafe
@NotNullByDefault
public class ContactListItem extends ContactItem
implements Comparable<ContactListItem> {
public class ContactListItem extends ContactItem {
private final boolean empty;
private final long timestamp;
private final int unread;
private boolean empty;
private long timestamp;
private int unread;
public ContactListItem(Contact contact, AuthorInfo authorInfo,
boolean connected, GroupCount count) {
super(contact, authorInfo, connected);
public ContactListItem(Contact contact, boolean connected,
GroupCount count) {
super(contact, connected);
this.empty = count.getMsgCount() == 0;
this.unread = count.getUnreadCount();
this.timestamp = count.getLatestMsgTime();
}
private ContactListItem(Contact contact, AuthorInfo authorInfo,
boolean connected, boolean empty, int unread, long timestamp) {
super(contact, authorInfo, connected);
this.empty = empty;
this.timestamp = timestamp;
this.unread = unread;
}
ContactListItem(ContactListItem item, boolean connected) {
this(item.getContact(), item.getAuthorInfo(), connected, item.empty,
item.unread, item.timestamp);
}
ContactListItem(ContactListItem item, ConversationMessageHeader h) {
this(item.getContact(), item.getAuthorInfo(), item.isConnected(), false,
h.isRead() ? item.unread : item.unread + 1,
Math.max(h.getTimestamp(), item.timestamp));
}
/**
* Creates a new copy of the given item with a new avatar
* referenced by the given attachment header.
*/
ContactListItem(ContactListItem item,
AttachmentHeader attachmentHeader) {
this(item.getContact(), new AuthorInfo(item.getAuthorInfo().getStatus(),
item.getAuthorInfo().getAlias(), attachmentHeader),
item.isConnected(), item.empty, item.unread, item.timestamp);
void addMessage(ConversationMessageHeader h) {
empty = false;
if (h.getTimestamp() > timestamp) timestamp = h.getTimestamp();
if (!h.isRead()) unread++;
}
boolean isEmpty() {
@@ -68,8 +41,4 @@ public class ContactListItem extends ContactItem
return unread;
}
@Override
public int compareTo(ContactListItem o) {
return Long.compare(o.getTimestamp(), timestamp);
}
}

View File

@@ -3,9 +3,11 @@ package org.briarproject.briar.android.contact;
import android.view.View;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Locale;
@@ -13,6 +15,7 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread;
import static androidx.core.view.ViewCompat.setTransitionName;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
@UiThread
@@ -36,8 +39,7 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
// unread count
int unreadCount = item.getUnreadCount();
if (unreadCount > 0) {
unread.setText(
String.format(Locale.getDefault(), "%d", unreadCount));
unread.setText(String.format(Locale.getDefault(), "%d", unreadCount));
unread.setVisibility(View.VISIBLE);
} else {
unread.setVisibility(View.INVISIBLE);
@@ -50,6 +52,10 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
long timestamp = item.getTimestamp();
date.setText(formatDate(date.getContext(), timestamp));
}
ContactId c = item.getContact().getId();
setTransitionName(avatar, UiUtils.getAvatarTransitionName(c));
setTransitionName(bulb, UiUtils.getBulbTransitionName(c));
}
}

View File

@@ -1,19 +0,0 @@
package org.briarproject.briar.android.contact;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class ContactListModule {
@Binds
@IntoMap
@ViewModelKey(ContactListViewModel.class)
abstract ViewModel bindContactListViewModel(
ContactListViewModel contactListViewModel);
}

View File

@@ -1,188 +0,0 @@
package org.briarproject.briar.android.contact;
import android.app.Application;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault
class ContactListViewModel extends DbViewModel implements EventListener {
private static final Logger LOG =
getLogger(ContactListViewModel.class.getName());
private final ContactManager contactManager;
private final AuthorManager authorManager;
private final ConversationManager conversationManager;
private final ConnectionRegistry connectionRegistry;
private final EventBus eventBus;
private final AndroidNotificationManager notificationManager;
private final MutableLiveData<LiveResult<List<ContactListItem>>>
contactListItems = new MutableLiveData<>();
private final MutableLiveData<Boolean> hasPendingContacts =
new MutableLiveData<>();
@Inject
ContactListViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, TransactionManager db,
AndroidExecutor androidExecutor, ContactManager contactManager,
AuthorManager authorManager,
ConversationManager conversationManager,
ConnectionRegistry connectionRegistry, EventBus eventBus,
AndroidNotificationManager notificationManager) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.contactManager = contactManager;
this.authorManager = authorManager;
this.conversationManager = conversationManager;
this.connectionRegistry = connectionRegistry;
this.eventBus = eventBus;
this.notificationManager = notificationManager;
this.eventBus.addListener(this);
}
@Override
protected void onCleared() {
super.onCleared();
eventBus.removeListener(this);
}
void loadContacts() {
loadList(this::loadContacts, contactListItems::setValue);
}
private List<ContactListItem> loadContacts(Transaction txn)
throws DbException {
long start = now();
List<ContactListItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts(txn)) {
ContactId id = c.getId();
AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c);
MessageTracker.GroupCount count =
conversationManager.getGroupCount(txn, id);
boolean connected = connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, authorInfo, connected, count));
}
Collections.sort(contacts);
logDuration(LOG, "Full load", start);
return contacts;
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading");
loadContacts();
} else if (e instanceof ContactConnectedEvent) {
updateItem(((ContactConnectedEvent) e).getContactId(),
item -> new ContactListItem(item, true), false);
} else if (e instanceof ContactDisconnectedEvent) {
updateItem(((ContactDisconnectedEvent) e).getContactId(),
item -> new ContactListItem(item, false), false);
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof ConversationMessageReceivedEvent) {
LOG.info("Conversation message received, updating item");
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), item -> new ContactListItem(item, h),
true);
} else if (e instanceof PendingContactAddedEvent ||
e instanceof PendingContactRemovedEvent) {
checkForPendingContacts();
} else if (e instanceof AvatarUpdatedEvent) {
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
updateItem(a.getContactId(), item -> new ContactListItem(item,
a.getAttachmentHeader()), false);
}
}
LiveData<LiveResult<List<ContactListItem>>> getContactListItems() {
return contactListItems;
}
LiveData<Boolean> getHasPendingContacts() {
return hasPendingContacts;
}
private void updateItem(ContactId c,
Function<ContactListItem, ContactListItem> replacer, boolean sort) {
List<ContactListItem> list = updateListItems(contactListItems,
itemToTest -> itemToTest.getContact().getId().equals(c),
replacer);
if (list == null) return;
if (sort) Collections.sort(list);
contactListItems.setValue(new LiveResult<>(list));
}
private void removeItem(ContactId c) {
List<ContactListItem> list = removeListItems(contactListItems,
itemToTest -> itemToTest.getContact().getId().equals(c));
if (list == null) return;
contactListItems.setValue(new LiveResult<>(list));
}
void checkForPendingContacts() {
runOnDbThreadOrLogException(() -> {
boolean hasPending = !contactManager.getPendingContacts().isEmpty();
hasPendingContacts.postValue(hasPending);
});
}
void clearAllContactNotifications() {
notificationManager.clearAllContactNotifications();
}
void clearAllContactAddedNotifications() {
notificationManager.clearAllContactAddedNotifications();
}
}

Some files were not shown because too many files have changed in this diff Show More