mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
121 Commits
deliver-te
...
1881-threa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21d6dfe817 | ||
|
|
c1e83b22c1 | ||
|
|
635008fb60 | ||
|
|
b78569119a | ||
|
|
8372bb01b2 | ||
|
|
766718e75c | ||
|
|
1c107a851b | ||
|
|
db53e79d1d | ||
|
|
21e56284fb | ||
|
|
d393b79ced | ||
|
|
6611d7c02e | ||
|
|
ab43dd4986 | ||
|
|
36a9174781 | ||
|
|
4663e727eb | ||
|
|
e2acd19ffd | ||
|
|
0befa6a823 | ||
|
|
01083f47ea | ||
|
|
a349bd146c | ||
|
|
4ffa9e191c | ||
|
|
e616fc3da7 | ||
|
|
aed5ac5bb4 | ||
|
|
cae53a9fcc | ||
|
|
6660625ba6 | ||
|
|
bf9ba13b68 | ||
|
|
a52c97ecf7 | ||
|
|
a2174e7677 | ||
|
|
d3cf3d680e | ||
|
|
cbb87aa00c | ||
|
|
53d985161f | ||
|
|
86002b0402 | ||
|
|
f75e789493 | ||
|
|
b22f302fdd | ||
|
|
c4a42760c8 | ||
|
|
8d92f36522 | ||
|
|
6c86873ea7 | ||
|
|
4fa9d654b5 | ||
|
|
3d303ccad5 | ||
|
|
b0d99a9f33 | ||
|
|
1a5e789bec | ||
|
|
97040c6299 | ||
|
|
301085c685 | ||
|
|
946c79d918 | ||
|
|
20418cfc7f | ||
|
|
7b09f0f98d | ||
|
|
97a7c8824b | ||
|
|
423684a14f | ||
|
|
09d91b522f | ||
|
|
64c0e9e9e4 | ||
|
|
15021bffef | ||
|
|
43c6ae4258 | ||
|
|
f819930570 | ||
|
|
aa00ba7220 | ||
|
|
19db58ee19 | ||
|
|
05f4d63356 | ||
|
|
6e5af2d3d3 | ||
|
|
00bf1eac0a | ||
|
|
8a10f16861 | ||
|
|
9bd7214d1d | ||
|
|
fce1247aa6 | ||
|
|
990f983ea9 | ||
|
|
6e57d7bb42 | ||
|
|
1b0cb532de | ||
|
|
fe7121b4ec | ||
|
|
5aa041f9e1 | ||
|
|
6939d8d230 | ||
|
|
c3cea37641 | ||
|
|
d0d2e0ed82 | ||
|
|
cf8f5c989f | ||
|
|
8b45e01c42 | ||
|
|
ec972e8a1d | ||
|
|
100791c3f3 | ||
|
|
83ac866cc1 | ||
|
|
ef9b22670d | ||
|
|
186ac30f37 | ||
|
|
5aa24414c6 | ||
|
|
dd6d72ed30 | ||
|
|
4344be2ca0 | ||
|
|
1e94af3ef3 | ||
|
|
cb69340749 | ||
|
|
f3d068414b | ||
|
|
5fdc7e7cc4 | ||
|
|
7569d5ffb3 | ||
|
|
deca5d56cc | ||
|
|
3d6b48bb34 | ||
|
|
0dc631b7a8 | ||
|
|
921e952b05 | ||
|
|
3b02797639 | ||
|
|
e2e67edbbe | ||
|
|
a9cd40faeb | ||
|
|
dd3c19aba2 | ||
|
|
e8ede55422 | ||
|
|
04517e942e | ||
|
|
9a25ad892d | ||
|
|
3457d8f9ab | ||
|
|
5fb2624ffa | ||
|
|
ed9a7bec2c | ||
|
|
ff70315d5c | ||
|
|
f197243273 | ||
|
|
6409a3b179 | ||
|
|
f882e46b33 | ||
|
|
efa63c306a | ||
|
|
205b4f77b2 | ||
|
|
015ecb1d99 | ||
|
|
fd86b73626 | ||
|
|
9048392d4e | ||
|
|
480aaaa35e | ||
|
|
002feb8e29 | ||
|
|
c6ba2b037a | ||
|
|
98788c7c80 | ||
|
|
e6f66ebc95 | ||
|
|
04485e58da | ||
|
|
97118fd92b | ||
|
|
ac4fbf202f | ||
|
|
b81495eac1 | ||
|
|
db90f75d2e | ||
|
|
bed3abfd40 | ||
|
|
0967f6c48e | ||
|
|
f9a8fcb207 | ||
|
|
eb3c2a3566 | ||
|
|
02ee678bab | ||
|
|
f6bdbb1b80 |
1
.idea/runConfigurations/All_tests.xml
generated
1
.idea/runConfigurations/All_tests.xml
generated
@@ -13,6 +13,7 @@
|
||||
<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>
|
||||
|
||||
14
.idea/runConfigurations/All_tests_in_briar_api.xml
generated
Normal file
14
.idea/runConfigurations/All_tests_in_briar_api.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<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>
|
||||
@@ -5,14 +5,14 @@ apply plugin: 'witness'
|
||||
apply from: 'witness.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 10213
|
||||
versionName "1.2.13"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
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;
|
||||
@@ -19,6 +23,11 @@ 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;
|
||||
@@ -36,16 +45,22 @@ 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 =
|
||||
Logger.getLogger(AndroidNetworkManager.class.getName());
|
||||
getLogger(AndroidNetworkManager.class.getName());
|
||||
|
||||
// See android.net.wifi.WifiManager
|
||||
private static final String WIFI_AP_STATE_CHANGED_ACTION =
|
||||
@@ -54,7 +69,8 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
||||
private final TaskScheduler scheduler;
|
||||
private final EventBus eventBus;
|
||||
private final Executor eventExecutor;
|
||||
private final Context appContext;
|
||||
private final Application app;
|
||||
private final ConnectivityManager connectivityManager;
|
||||
private final AtomicReference<Cancellable> connectivityCheck =
|
||||
new AtomicReference<>();
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
@@ -67,7 +83,9 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
||||
this.scheduler = scheduler;
|
||||
this.eventBus = eventBus;
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.appContext = app.getApplicationContext();
|
||||
this.app = app;
|
||||
connectivityManager = (ConnectivityManager)
|
||||
requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,24 +100,82 @@ 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);
|
||||
appContext.registerReceiver(networkStateReceiver, filter);
|
||||
app.registerReceiver(networkStateReceiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() {
|
||||
if (networkStateReceiver != null)
|
||||
appContext.unregisterReceiver(networkStateReceiver);
|
||||
app.unregisterReceiver(networkStateReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkStatus getNetworkStatus() {
|
||||
ConnectivityManager cm = (ConnectivityManager)
|
||||
appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||
if (cm == null) throw new AssertionError();
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
||||
boolean connected = net != null && net.isConnected();
|
||||
boolean wifi = connected && net.getType() == TYPE_WIFI;
|
||||
return new NetworkStatus(connected, wifi);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConnectionStatus() {
|
||||
|
||||
@@ -6,8 +6,6 @@ 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;
|
||||
@@ -17,12 +15,10 @@ 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;
|
||||
|
||||
@@ -52,15 +48,6 @@ 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())
|
||||
|
||||
@@ -2,7 +2,6 @@ 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;
|
||||
|
||||
@@ -106,14 +105,21 @@ class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
|
||||
|
||||
private String getWakeLockTag(Context ctx) {
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
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";
|
||||
}
|
||||
if (isInstalled(pm, "com.huawei.powergenie")) {
|
||||
return "LocationManagerService";
|
||||
} else if (isInstalled(pm, "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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,4 +6,7 @@ package org.briarproject.bramble.api;
|
||||
public interface FeatureFlags {
|
||||
|
||||
boolean shouldEnableImageAttachments();
|
||||
|
||||
boolean shouldEnableProfilePictures();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
public interface ThrowingRunnable<T extends Throwable> {
|
||||
|
||||
void run() throws T;
|
||||
}
|
||||
@@ -11,7 +11,6 @@ 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;
|
||||
|
||||
@@ -179,6 +178,11 @@ 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.
|
||||
*/
|
||||
@@ -215,16 +219,6 @@ 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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,11 +8,12 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class NetworkStatus {
|
||||
|
||||
private final boolean connected, wifi;
|
||||
private final boolean connected, wifi, ipv6Only;
|
||||
|
||||
public NetworkStatus(boolean connected, boolean wifi) {
|
||||
public NetworkStatus(boolean connected, boolean wifi, boolean ipv6Only) {
|
||||
this.connected = connected;
|
||||
this.wifi = wifi;
|
||||
this.ipv6Only = ipv6Only;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
@@ -22,4 +23,8 @@ public class NetworkStatus {
|
||||
public boolean isWifi() {
|
||||
return wifi;
|
||||
}
|
||||
|
||||
public boolean isIpv6Only() {
|
||||
return ipv6Only;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,4 +29,12 @@ 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ public interface DevReporter {
|
||||
|
||||
/**
|
||||
* Sends any reports previously stored on disk.
|
||||
*
|
||||
* @return The number of reports that were sent.
|
||||
*/
|
||||
void sendReports();
|
||||
int sendReports();
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
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")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,9 +20,7 @@ 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;
|
||||
|
||||
@@ -40,10 +38,6 @@ 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
|
||||
@@ -213,6 +207,11 @@ 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));
|
||||
@@ -256,25 +255,6 @@ 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) {
|
||||
|
||||
@@ -11,6 +11,7 @@ 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;
|
||||
@@ -52,7 +53,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();
|
||||
return "briar://" + Base32.encode(raw).toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
private PublicKey parseHandshakeLink(String link) throws FormatException {
|
||||
|
||||
@@ -863,6 +863,7 @@ 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);
|
||||
@@ -879,7 +880,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi
|
||||
+ ", IPv6 only: " + ipv6Only);
|
||||
if (country.isEmpty()) LOG.info("Country code unknown");
|
||||
else LOG.info("Country code: " + country);
|
||||
LOG.info("Charging: " + charging);
|
||||
@@ -916,7 +918,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
enableNetwork = true;
|
||||
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
||||
(automatic && bridgesWork)) {
|
||||
if (circumventionProvider.needsMeek(country)) {
|
||||
if (ipv6Only ||
|
||||
circumventionProvider.needsMeek(country)) {
|
||||
LOG.info("Using meek bridges");
|
||||
enableBridges = true;
|
||||
useMeek = true;
|
||||
@@ -942,6 +945,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (enableNetwork) {
|
||||
enableBridges(enableBridges, useMeek);
|
||||
enableConnectionPadding(enableConnectionPadding);
|
||||
useIpv6(ipv6Only);
|
||||
}
|
||||
enableNetwork(enableNetwork);
|
||||
} catch (IOException e) {
|
||||
@@ -954,6 +958,11 @@ 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 {
|
||||
|
||||
@@ -15,6 +15,9 @@ 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;
|
||||
|
||||
@@ -43,9 +46,9 @@ class TransportPropertyValidator extends BdfMessageValidator {
|
||||
clientHelper.parseAndValidateTransportProperties(dictionary);
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put("transportId", transportId);
|
||||
meta.put("version", version);
|
||||
meta.put("local", false);
|
||||
meta.put(MSG_KEY_TRANSPORT_ID, transportId);
|
||||
meta.put(MSG_KEY_VERSION, version);
|
||||
meta.put(MSG_KEY_LOCAL, false);
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ 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;
|
||||
|
||||
@@ -100,11 +101,12 @@ class DevReporterImpl implements DevReporter, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendReports() {
|
||||
public int sendReports() {
|
||||
File reportDir = devConfig.getReportDir();
|
||||
File[] reports = reportDir.listFiles();
|
||||
int reportsSent = 0;
|
||||
if (reports == null || reports.length == 0)
|
||||
return; // No reports to send
|
||||
return reportsSent; // No reports to send
|
||||
|
||||
LOG.info("Sending reports to developers");
|
||||
for (File f : reports) {
|
||||
@@ -116,13 +118,15 @@ 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;
|
||||
return reportsSent;
|
||||
}
|
||||
}
|
||||
LOG.info("Reports sent");
|
||||
if (LOG.isLoggable(INFO)) LOG.info(reportsSent + " report(s) sent");
|
||||
return reportsSent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
interface ThrowingRunnable<T extends Throwable> {
|
||||
|
||||
void run() throws T;
|
||||
}
|
||||
@@ -8,18 +8,15 @@ 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;
|
||||
|
||||
@@ -31,10 +28,6 @@ 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;
|
||||
@@ -46,7 +39,6 @@ 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 {
|
||||
@@ -212,75 +204,6 @@ 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);
|
||||
|
||||
@@ -22,6 +22,17 @@ public class BrambleCoreIntegrationTestModule {
|
||||
|
||||
@Provides
|
||||
FeatureFlags provideFeatureFlags() {
|
||||
return () -> true;
|
||||
return new FeatureFlags() {
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableImageAttachments() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableProfilePictures() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ 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;
|
||||
@@ -14,8 +16,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
|
||||
@@ -23,7 +25,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
class JavaNetworkManager implements NetworkManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(JavaNetworkManager.class.getName());
|
||||
getLogger(JavaNetworkManager.class.getName());
|
||||
|
||||
@Inject
|
||||
JavaNetworkManager() {
|
||||
@@ -31,26 +33,28 @@ class JavaNetworkManager implements NetworkManager {
|
||||
|
||||
@Override
|
||||
public NetworkStatus getNetworkStatus() {
|
||||
boolean connected = false;
|
||||
boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false;
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
|
||||
if (interfaces != null) {
|
||||
if (interfaces == null) {
|
||||
LOG.info("No network interfaces");
|
||||
} else {
|
||||
for (NetworkInterface i : list(interfaces)) {
|
||||
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.");
|
||||
}
|
||||
if (i.isLoopback() || !i.isUp()) continue;
|
||||
for (InetAddress addr : list(i.getInetAddresses())) {
|
||||
connected = true;
|
||||
break;
|
||||
if (addr instanceof Inet4Address) {
|
||||
hasIpv4 = true;
|
||||
} else if (!addr.isMulticastAddress()) {
|
||||
hasIpv6Unicast = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
return new NetworkStatus(connected, false);
|
||||
return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ def getStdout = { command, defaultValue ->
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 10213
|
||||
versionName "1.2.13"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
buildConfigField "String", "GitHash",
|
||||
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
|
||||
@@ -100,7 +100,6 @@ 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'
|
||||
@@ -109,7 +108,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.10.0'
|
||||
def glideVersion = '4.11.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
|
||||
@@ -127,10 +126,11 @@ 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.12'
|
||||
testImplementation 'junit:junit:4.13.1'
|
||||
testImplementation "org.jmock:jmock:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
||||
@@ -142,11 +142,9 @@ 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.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'
|
||||
}
|
||||
androidTestImplementation 'junit:junit:4.13.1'
|
||||
androidTestScreenshotImplementation 'tools.fastlane:screengrab:2.0.0'
|
||||
androidTestScreenshotImplementation 'com.jraska:falcon:2.1.1'
|
||||
androidTestScreenshotImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -15,6 +16,7 @@ import dagger.Component;
|
||||
@Component(modules = {
|
||||
AppModule.class,
|
||||
AttachmentModule.class,
|
||||
MediaModule.class,
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
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.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
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.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;
|
||||
@@ -24,16 +29,28 @@ 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());
|
||||
|
||||
private final ImageHelper imageHelper = new ImageHelperImpl();
|
||||
private final AttachmentRetriever retriever =
|
||||
new AttachmentRetrieverImpl(null, null, dimensions, imageHelper,
|
||||
new ImageSizeCalculator(imageHelper));
|
||||
@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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallJpegImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("kitten_small.jpg");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -49,7 +66,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testBigJpegImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("kitten_original.jpg");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -65,7 +82,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testSmallPngImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/png");
|
||||
InputStream is = getAssetInputStream("kitten.png");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -81,7 +98,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testUberGif() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("uber.gif");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -96,7 +113,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testLottaPixels() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("lottapixel.jpg");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -111,7 +128,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testImageIoCrash() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/png");
|
||||
InputStream is = getAssetInputStream("image_io_crash.png");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -126,7 +143,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testGimpCrash() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("gimp_crash.gif");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -141,7 +158,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testOptiPngAfl() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("opti_png_afl.gif");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -156,7 +173,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testLibrawError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("libraw_error.jpg");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -165,7 +182,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testSmallAnimatedGifMaxDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("animated.gif");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -180,7 +197,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testSmallAnimatedGifHugeDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("animated2.gif");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -195,7 +212,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testSmallGifLargeDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("error_large.gif");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -210,7 +227,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testHighError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("error_high.jpg");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
@@ -225,7 +242,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testWideError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
package org.briarproject.briar.android.attachment.media;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -9,8 +9,13 @@ import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AttachmentCreationTaskTest
|
||||
extends AbstractAttachmentCreationTaskTest {
|
||||
public class ImageCompressorTest
|
||||
extends AbstractImageCompressorTest {
|
||||
|
||||
@Override
|
||||
protected void inject(AbstractImageCompressorComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompressSmallPng() throws Exception {
|
||||
@@ -0,0 +1,80 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
package org.briarproject.briar.android.attachment.media;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -17,11 +17,16 @@ import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class PngSuiteAttachmentCreationTaskTest
|
||||
extends AbstractAttachmentCreationTaskTest {
|
||||
public class PngSuiteImageCompressorTest
|
||||
extends AbstractImageCompressorTest {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(PngSuiteAttachmentCreationTaskTest.class.getName());
|
||||
getLogger(PngSuiteImageCompressorTest.class.getName());
|
||||
|
||||
@Override
|
||||
protected void inject(AbstractImageCompressorComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static Iterable<String> data() throws IOException {
|
||||
@@ -34,14 +39,14 @@ public class PngSuiteAttachmentCreationTaskTest
|
||||
|
||||
private final String filename;
|
||||
|
||||
public PngSuiteAttachmentCreationTaskTest(String filename) {
|
||||
public PngSuiteImageCompressorTest(String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPngSuiteCompress() throws Exception {
|
||||
assumeTrue(isOptionalTestEnabled(
|
||||
PngSuiteAttachmentCreationTaskTest.class));
|
||||
PngSuiteImageCompressorTest.class));
|
||||
LOG.info("Testing " + filename);
|
||||
if (filename.startsWith("x")) {
|
||||
try {
|
||||
@@ -0,0 +1,80 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
|
||||
@@ -16,6 +17,7 @@ import dagger.Component;
|
||||
@Component(modules = {
|
||||
AppModule.class,
|
||||
AttachmentModule.class,
|
||||
MediaModule.class,
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
@@ -26,6 +28,7 @@ public interface BriarUiTestComponent extends AndroidComponent {
|
||||
void inject(SetupDataTest test);
|
||||
|
||||
void inject(ConversationActivityScreenshotTest test);
|
||||
|
||||
void inject(SettingsActivityScreenshotTest test);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
Contact bob = testDataCreator.addContact(bobName, true);
|
||||
|
||||
// TODO add messages
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,30 +65,43 @@
|
||||
|
||||
<service
|
||||
android:name="org.briarproject.briar.android.NotificationCleanupService"
|
||||
android:exported="false"></service>
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.reporting.DevReportActivity"
|
||||
android:name="org.briarproject.briar.android.reporting.CrashReportActivity"
|
||||
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: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>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
|
||||
android:label="@string/app_name"></activity>
|
||||
android:label="@string/app_name" />
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.login.StartupActivity"
|
||||
android:label="@string/app_name"></activity>
|
||||
android:label="@string/app_name" />
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.account.SetupActivity"
|
||||
android:label="@string/setup_title"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible"></activity>
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
|
||||
@@ -346,7 +359,7 @@
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.StartupFailureActivity"
|
||||
android:label="@string/startup_failed_activity_title"></activity>
|
||||
android:label="@string/startup_failed_activity_title" />
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.settings.SettingsActivity"
|
||||
@@ -412,11 +425,11 @@
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.logout.ExitActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"></activity>
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
|
||||
<activity
|
||||
android:name=".android.logout.HideUiActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"></activity>
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
|
||||
<activity
|
||||
android:name=".android.account.UnlockActivity"
|
||||
@@ -436,4 +449,29 @@
|
||||
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>
|
||||
|
||||
@@ -14,6 +14,7 @@ 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;
|
||||
@@ -31,14 +32,15 @@ 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;
|
||||
@@ -47,6 +49,7 @@ 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;
|
||||
@@ -71,7 +74,8 @@ import dagger.Component;
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
AppModule.class,
|
||||
AttachmentModule.class
|
||||
AttachmentModule.class,
|
||||
MediaModule.class
|
||||
})
|
||||
public interface AndroidComponent
|
||||
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
||||
@@ -86,12 +90,18 @@ public interface AndroidComponent
|
||||
@DatabaseExecutor
|
||||
Executor databaseExecutor();
|
||||
|
||||
TransactionManager transactionManager();
|
||||
|
||||
MessageTracker messageTracker();
|
||||
|
||||
LifecycleManager lifecycleManager();
|
||||
|
||||
IdentityManager identityManager();
|
||||
|
||||
AttachmentReader attachmentReader();
|
||||
|
||||
AuthorManager authorManager();
|
||||
|
||||
PluginManager pluginManager();
|
||||
|
||||
EventBus eventBus();
|
||||
@@ -173,8 +183,6 @@ public interface AndroidComponent
|
||||
|
||||
void inject(BriarService briarService);
|
||||
|
||||
void inject(BriarReportSender briarReportSender);
|
||||
|
||||
void inject(NotificationCleanupService notificationCleanupService);
|
||||
|
||||
void inject(EmojiTextInputView textInputView);
|
||||
|
||||
@@ -109,7 +109,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@Nullable
|
||||
private GroupId blockedGroup = null;
|
||||
private boolean blockSignInReminder = false;
|
||||
private boolean blockBlogs = false;
|
||||
private boolean blockForums = false, blockGroups = false,
|
||||
blockBlogs = false;
|
||||
private long lastSound = 0;
|
||||
|
||||
private volatile Settings settings = new Settings();
|
||||
@@ -223,8 +224,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;
|
||||
@@ -385,6 +386,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
|
||||
@UiThread
|
||||
private void showGroupMessageNotification(GroupId g) {
|
||||
if (blockGroups) return;
|
||||
if (g.equals(blockedGroup)) return;
|
||||
groupCounts.add(g);
|
||||
updateGroupMessageNotification(true);
|
||||
@@ -452,6 +454,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
|
||||
@UiThread
|
||||
private void showForumPostNotification(GroupId g) {
|
||||
if (blockForums) return;
|
||||
if (g.equals(blockedGroup)) return;
|
||||
forumCounts.add(g);
|
||||
updateForumPostNotification(true);
|
||||
@@ -681,6 +684,26 @@ 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);
|
||||
|
||||
@@ -27,15 +27,26 @@ 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;
|
||||
@@ -60,10 +71,20 @@ 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
|
||||
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,
|
||||
})
|
||||
public class AppModule {
|
||||
|
||||
@@ -175,6 +196,12 @@ public class AppModule {
|
||||
return devConfig;
|
||||
}
|
||||
|
||||
@Provides
|
||||
TestAvatarCreator provideTestAvatarCreator(
|
||||
TestAvatarCreatorImpl testAvatarCreator) {
|
||||
return testAvatarCreator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SharedPreferences provideSharedPreferences(Application app) {
|
||||
// FIXME unify this with getDefaultSharedPreferences()
|
||||
@@ -196,7 +223,10 @@ public class AppModule {
|
||||
ScreenFilterMonitor provideScreenFilterMonitor(
|
||||
LifecycleManager lifecycleManager,
|
||||
ScreenFilterMonitorImpl screenFilterMonitor) {
|
||||
lifecycleManager.registerService(screenFilterMonitor);
|
||||
if (SDK_INT <= 29) {
|
||||
// this keeps track of installed apps and does not work on API 30+
|
||||
lifecycleManager.registerService(screenFilterMonitor);
|
||||
}
|
||||
return screenFilterMonitor;
|
||||
}
|
||||
|
||||
@@ -235,6 +265,17 @@ public class AppModule {
|
||||
|
||||
@Provides
|
||||
FeatureFlags provideFeatureFlags() {
|
||||
return () -> IS_DEBUG_BUILD;
|
||||
return new FeatureFlags() {
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableImageAttachments() {
|
||||
return IS_DEBUG_BUILD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableProfilePictures() {
|
||||
return IS_DEBUG_BUILD;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,19 +14,13 @@ 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.BriarReportPrimer;
|
||||
import org.briarproject.briar.android.reporting.BriarReportSenderFactory;
|
||||
import org.briarproject.briar.android.reporting.DevReportActivity;
|
||||
import org.briarproject.briar.android.reporting.BriarExceptionHandler;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -34,50 +28,14 @@ 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 {
|
||||
|
||||
@@ -85,12 +43,15 @@ 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.
|
||||
@@ -98,7 +59,6 @@ public class BriarApplicationImpl extends Application
|
||||
super.attachBaseContext(
|
||||
Localizer.getInstance().setLocale(base));
|
||||
setTheme(base, prefs);
|
||||
ACRA.init(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,7 +104,7 @@ public class BriarApplicationImpl extends Application
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
Localizer.getInstance().setLocale(this);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ 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;
|
||||
@@ -246,10 +248,13 @@ 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)) {
|
||||
|
||||
@@ -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<>();
|
||||
List<PackageInfo> packageInfos =
|
||||
@SuppressLint("QueryPermissionsNeeded") List<PackageInfo> packageInfos =
|
||||
pm.getInstalledPackages(GET_PERMISSIONS);
|
||||
for (PackageInfo packageInfo : packageInfos) {
|
||||
if (!allowed.contains(packageInfo.packageName)
|
||||
|
||||
@@ -81,8 +81,7 @@ public class AuthorNameFragment extends SetupFragment {
|
||||
public void onClick(View view) {
|
||||
Editable text = authorNameInput.getText();
|
||||
if (text != null) {
|
||||
setupController.setAuthorName(text.toString().trim());
|
||||
setupController.showPasswordFragment();
|
||||
viewModel.setAuthorName(text.toString().trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,9 +104,15 @@ public class DozeFragment extends SetupFragment
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
next.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
setupController.createAccount();
|
||||
setNextClicked();
|
||||
viewModel.dozeExceptionConfirmed();
|
||||
}
|
||||
|
||||
@Override
|
||||
void setNextClicked() {
|
||||
super.setNextClicked();
|
||||
|
||||
next.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.briarproject.briar.android.account;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
interface DozeHelper {
|
||||
|
||||
boolean needToShowDozeFragment(Context context);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.briarproject.briar.android.account;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class DozeHelperModule {
|
||||
|
||||
@Provides
|
||||
DozeHelper provideDozeHelper() {
|
||||
return new DozeHelperImpl();
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ 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;
|
||||
@@ -56,7 +55,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);
|
||||
@@ -71,7 +70,7 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
passwordConfirmation.addTextChangedListener(this);
|
||||
nextButton.setOnClickListener(this);
|
||||
|
||||
if (!setupController.needToShowDozeFragment()) {
|
||||
if (!viewModel.needToShowDozeFragment()) {
|
||||
nextButton.setText(R.string.create_account_button);
|
||||
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
|
||||
}
|
||||
@@ -97,7 +96,7 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
|
||||
strengthMeter
|
||||
.setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE);
|
||||
float strength = setupController.estimatePasswordStrength(password1);
|
||||
float strength = viewModel.estimatePasswordStrength(password1);
|
||||
strengthMeter.setStrength(strength);
|
||||
boolean strongEnough = strength >= QUITE_WEAK;
|
||||
|
||||
@@ -117,14 +116,20 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
IBinder token = passwordEntry.getWindowToken();
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
setupController.setPassword(passwordEntry.getText().toString());
|
||||
if (setupController.needToShowDozeFragment()) {
|
||||
setupController.showDozeFragment();
|
||||
} else {
|
||||
nextButton.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
setupController.createAccount();
|
||||
}
|
||||
|
||||
setNextClicked();
|
||||
viewModel.setPassword(passwordEntry.getText().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
void setNextClicked() {
|
||||
super.setNextClicked();
|
||||
|
||||
passwordEntry.setFocusable(false);
|
||||
passwordConfirmation.setFocusable(false);
|
||||
if (!viewModel.needToShowDozeFragment()) {
|
||||
nextButton.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ 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;
|
||||
@@ -15,28 +14,36 @@ 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
|
||||
AccountManager accountManager;
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
SetupViewModel viewModel;
|
||||
|
||||
@Inject
|
||||
SetupController setupController;
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
@Nullable
|
||||
private String authorName, password;
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(SetupViewModel.class);
|
||||
viewModel.getState().observeEvent(this, this::onStateChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
@@ -44,58 +51,27 @@ 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);
|
||||
}
|
||||
|
||||
if (state == null) {
|
||||
if (accountManager.accountExists()) throw new AssertionError();
|
||||
private void onStateChanged(SetupViewModel.State state) {
|
||||
if (state == AUTHOR_NAME) {
|
||||
showInitialFragment(AuthorNameFragment.newInstance());
|
||||
} else {
|
||||
authorName = state.getString(STATE_KEY_AUTHOR_NAME);
|
||||
password = state.getString(STATE_KEY_PASSWORD);
|
||||
} else if (state == SET_PASSWORD) {
|
||||
showPasswordFragment();
|
||||
} else if (state == DOZE) {
|
||||
showDozeFragment();
|
||||
} else if (state == CREATED || state == FAILED) {
|
||||
// TODO: Show an error if failed
|
||||
showApp();
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
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;
|
||||
@@ -17,7 +19,10 @@ 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;
|
||||
@@ -29,8 +34,40 @@ 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
|
||||
SetupController setupController;
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ 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;
|
||||
@@ -33,7 +32,6 @@ 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;
|
||||
@@ -51,7 +49,6 @@ 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;
|
||||
@@ -60,12 +57,15 @@ 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,15 +86,11 @@ 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.class
|
||||
SharingModule.SharingLegacyModule.class
|
||||
}, dependencies = AndroidComponent.class)
|
||||
public interface ActivityComponent {
|
||||
|
||||
@@ -184,6 +180,8 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(PendingContactListActivity activity);
|
||||
|
||||
void inject(CrashReportActivity crashReportActivity);
|
||||
|
||||
// Fragments
|
||||
|
||||
void inject(AuthorNameFragment fragment);
|
||||
@@ -234,4 +232,10 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(ImageFragment imageFragment);
|
||||
|
||||
void inject(ReportFormFragment reportFormFragment);
|
||||
|
||||
void inject(CrashFragment crashFragment);
|
||||
|
||||
void inject(ConfirmAvatarDialogFragment fragment);
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ 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;
|
||||
@@ -35,13 +33,6 @@ public class ActivityModule {
|
||||
return activity;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
SetupController provideSetupController(
|
||||
SetupControllerImpl setupController) {
|
||||
return setupController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
protected BriarController provideBriarController(
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -15,10 +14,8 @@ 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;
|
||||
@@ -40,9 +37,11 @@ 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;
|
||||
@@ -50,7 +49,6 @@ import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
|
||||
/**
|
||||
* Warning: Some activities don't extend {@link BaseActivity}.
|
||||
* E.g. {@link DevReportActivity}
|
||||
*/
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -88,7 +86,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
activityComponent = DaggerActivityComponent.builder()
|
||||
.androidComponent(applicationComponent)
|
||||
.activityModule(getActivityModule())
|
||||
.forumModule(getForumModule())
|
||||
.build();
|
||||
injectActivity(activityComponent);
|
||||
super.onCreate(state);
|
||||
@@ -123,10 +120,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
return new ActivityModule(this);
|
||||
}
|
||||
|
||||
protected ForumModule getForumModule() {
|
||||
return new ForumModule();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -202,9 +195,15 @@ 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 = screenFilterMonitor.getApps();
|
||||
// If all overlay apps have been allowed, allow the tap
|
||||
if (apps.isEmpty()) return true;
|
||||
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();
|
||||
}
|
||||
// Show dialog unless onSaveInstanceState() has been called, see #1112
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (!fm.isStateSaved()) {
|
||||
@@ -241,7 +240,7 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void handleDbException(DbException e) {
|
||||
public void handleException(Exception e) {
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
@@ -266,7 +265,12 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
private void protectToolbar() {
|
||||
findToolbar();
|
||||
if (toolbar != null) {
|
||||
boolean filter = !screenFilterMonitor.getApps().isEmpty();
|
||||
boolean filter;
|
||||
if (SDK_INT <= 29) {
|
||||
filter = !screenFilterMonitor.getApps().isEmpty();
|
||||
} else {
|
||||
filter = true;
|
||||
}
|
||||
UiUtils.setFilterTouchesWhenObscured(toolbar, filter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,10 +129,6 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
lockManager.onActivityStop();
|
||||
}
|
||||
|
||||
protected boolean signedIn() {
|
||||
return briarController.accountSignedIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transition animations.
|
||||
*
|
||||
@@ -164,7 +160,6 @@ 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);
|
||||
|
||||
@@ -16,5 +16,6 @@ public interface RequestCodes {
|
||||
int REQUEST_KEYGUARD_UNLOCK = 12;
|
||||
int REQUEST_ATTACH_IMAGE = 13;
|
||||
int REQUEST_SAVE_ATTACHMENT = 14;
|
||||
int REQUEST_AVATAR_IMAGE = 15;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
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.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.android.attachment.media.ImageCompressor;
|
||||
import org.briarproject.briar.api.attachment.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;
|
||||
@@ -35,19 +26,16 @@ 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 Logger LOG =
|
||||
private static final Logger LOG =
|
||||
getLogger(AttachmentCreationTask.class.getName());
|
||||
|
||||
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
|
||||
|
||||
private final MessagingManager messagingManager;
|
||||
private final ContentResolver contentResolver;
|
||||
private final ImageSizeCalculator imageSizeCalculator;
|
||||
private final ImageCompressor imageCompressor;
|
||||
private final GroupId groupId;
|
||||
private final Collection<Uri> uris;
|
||||
private final boolean needsSize;
|
||||
@@ -59,11 +47,11 @@ class AttachmentCreationTask {
|
||||
AttachmentCreationTask(MessagingManager messagingManager,
|
||||
ContentResolver contentResolver,
|
||||
AttachmentCreator attachmentCreator,
|
||||
ImageSizeCalculator imageSizeCalculator,
|
||||
ImageCompressor imageCompressor,
|
||||
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
|
||||
this.messagingManager = messagingManager;
|
||||
this.contentResolver = contentResolver;
|
||||
this.imageSizeCalculator = imageSizeCalculator;
|
||||
this.imageCompressor = imageCompressor;
|
||||
this.groupId = groupId;
|
||||
this.uris = uris;
|
||||
this.needsSize = needsSize;
|
||||
@@ -110,66 +98,19 @@ class AttachmentCreationTask {
|
||||
String contentType = contentResolver.getType(uri);
|
||||
if (contentType == null) throw new IOException("null content type");
|
||||
if (!asList(getSupportedImageContentTypes()).contains(contentType)) {
|
||||
String uriString = uri.toString();
|
||||
throw new UnsupportedMimeTypeException("", contentType, uriString);
|
||||
throw new UnsupportedMimeTypeException(contentType, uri);
|
||||
}
|
||||
InputStream is = contentResolver.openInputStream(uri);
|
||||
if (is == null) throw new IOException();
|
||||
is = compressImage(is, contentType);
|
||||
contentType = "image/jpeg";
|
||||
is = imageCompressor
|
||||
.compressImage(is, contentType);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
AttachmentHeader h = messagingManager
|
||||
.addLocalAttachment(groupId, timestamp, contentType, is);
|
||||
.addLocalAttachment(groupId, timestamp,
|
||||
ImageCompressor.MIME_TYPE, 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -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.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.FileTooBigException;
|
||||
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.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.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||
import static org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE;
|
||||
|
||||
@NotNullByDefault
|
||||
class AttachmentCreatorImpl implements AttachmentCreator {
|
||||
|
||||
private static Logger LOG =
|
||||
private static final 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 ImageSizeCalculator imageSizeCalculator;
|
||||
private final ImageCompressor imageCompressor;
|
||||
|
||||
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,
|
||||
ImageSizeCalculator imageSizeCalculator) {
|
||||
ImageCompressor imageCompressor) {
|
||||
this.app = app;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.messagingManager = messagingManager;
|
||||
this.retriever = retriever;
|
||||
this.imageSizeCalculator = imageSizeCalculator;
|
||||
this.imageCompressor = imageCompressor;
|
||||
}
|
||||
|
||||
@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, imageSizeCalculator, id,
|
||||
app.getContentResolver(), this, imageCompressor, id,
|
||||
uris, needsSize);
|
||||
ioExecutor.execute(() -> task.storeAttachments());
|
||||
});
|
||||
|
||||
@@ -4,8 +4,9 @@ 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.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@@ -78,6 +79,9 @@ 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);
|
||||
@@ -88,7 +92,7 @@ public class AttachmentItem implements Parcelable {
|
||||
thumbnailWidth = in.readInt();
|
||||
thumbnailHeight = in.readInt();
|
||||
state = State.valueOf(requireNonNull(in.readString()));
|
||||
header = new AttachmentHeader(messageId, mimeType);
|
||||
header = new AttachmentHeader(groupId, messageId, mimeType);
|
||||
}
|
||||
|
||||
public AttachmentHeader getHeader() {
|
||||
@@ -142,6 +146,7 @@ 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);
|
||||
|
||||
@@ -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.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -12,16 +12,6 @@ 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());
|
||||
|
||||
@@ -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.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.attachment.Attachment;
|
||||
import org.briarproject.briar.api.attachment.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.
|
||||
|
||||
@@ -6,9 +6,12 @@ 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.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
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.PrivateMessageHeader;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
@@ -43,7 +46,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
|
||||
@DatabaseExecutor
|
||||
private final Executor dbExecutor;
|
||||
private final MessagingManager messagingManager;
|
||||
private final AttachmentReader attachmentReader;
|
||||
private final ImageHelper imageHelper;
|
||||
private final ImageSizeCalculator imageSizeCalculator;
|
||||
private final int defaultSize;
|
||||
@@ -57,11 +60,10 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
|
||||
@Inject
|
||||
AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
MessagingManager messagingManager,
|
||||
AttachmentDimensions dimensions, ImageHelper imageHelper,
|
||||
ImageSizeCalculator imageSizeCalculator) {
|
||||
AttachmentReader attachmentReader, AttachmentDimensions dimensions,
|
||||
ImageHelper imageHelper, ImageSizeCalculator imageSizeCalculator) {
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.messagingManager = messagingManager;
|
||||
this.attachmentReader = attachmentReader;
|
||||
this.imageHelper = imageHelper;
|
||||
this.imageSizeCalculator = imageSizeCalculator;
|
||||
defaultSize = dimensions.defaultSize;
|
||||
@@ -75,7 +77,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
@DatabaseExecutor
|
||||
public Attachment getMessageAttachment(AttachmentHeader h)
|
||||
throws DbException {
|
||||
return messagingManager.getAttachment(h);
|
||||
return attachmentReader.getAttachment(h);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,13 +88,11 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
boolean needsSize = headers.size() == 1;
|
||||
for (AttachmentHeader h : headers) {
|
||||
// try cache for existing item live data
|
||||
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());
|
||||
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());
|
||||
}
|
||||
|
||||
// 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 = messagingManager.getAttachment(h);
|
||||
Attachment a = attachmentReader.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 = messagingManager.getAttachment(h);
|
||||
a = attachmentReader.getAttachment(h);
|
||||
item = createAttachmentItem(a, needsSize);
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Attachment not received yet");
|
||||
@@ -210,26 +210,30 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
|
||||
private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) {
|
||||
// calculate thumbnail size
|
||||
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
||||
if (!size.error) {
|
||||
Size thumbnailSize =
|
||||
new Size(defaultSize, defaultSize, size.getMimeType());
|
||||
if (!size.hasError()) {
|
||||
thumbnailSize =
|
||||
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||
getThumbnailSize(size.getWidth(), size.getHeight(),
|
||||
size.getMimeType());
|
||||
}
|
||||
// get file extension
|
||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||
boolean hasError = extension == null || size.error;
|
||||
if (!h.getContentType().equals(size.mimeType)) {
|
||||
String extension =
|
||||
imageHelper.getExtensionFromMimeType(size.getMimeType());
|
||||
boolean hasError = extension == null || size.hasError();
|
||||
if (!h.getContentType().equals(size.getMimeType())) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Header has different mime type (" +
|
||||
h.getContentType() + ") than image (" + size.mimeType +
|
||||
").");
|
||||
h.getContentType() + ") than image (" +
|
||||
size.getMimeType() + ").");
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
if (extension == null) extension = "";
|
||||
State state = hasError ? ERROR : AVAILABLE;
|
||||
return new AttachmentItem(h, size.width, size.height,
|
||||
extension, thumbnailSize.width, thumbnailSize.height, state);
|
||||
return new AttachmentItem(h, size.getWidth(), size.getHeight(),
|
||||
extension, thumbnailSize.getWidth(), thumbnailSize.getHeight(),
|
||||
state);
|
||||
}
|
||||
|
||||
private Size getThumbnailSize(int width, int height, String mimeType) {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
package org.briarproject.briar.android.attachment.media;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
package org.briarproject.briar.android.attachment.media;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.webkit.MimeTypeMap;
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
package org.briarproject.briar.android.attachment.media;
|
||||
|
||||
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||
import org.briarproject.briar.android.attachment.media.ImageHelper.DecodeResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -23,20 +23,21 @@ import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImageSizeCalculator {
|
||||
class ImageSizeCalculatorImpl implements ImageSizeCalculator {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ImageSizeCalculator.class.getName());
|
||||
getLogger(ImageSizeCalculatorImpl.class.getName());
|
||||
|
||||
private static final int READ_LIMIT = 1024 * 8192;
|
||||
|
||||
private final ImageHelper imageHelper;
|
||||
|
||||
ImageSizeCalculator(ImageHelper imageHelper) {
|
||||
ImageSizeCalculatorImpl(ImageHelper imageHelper) {
|
||||
this.imageHelper = imageHelper;
|
||||
}
|
||||
|
||||
Size getSize(InputStream is, String contentType) {
|
||||
@Override
|
||||
public Size getSize(InputStream is, String contentType) {
|
||||
Size size = new Size();
|
||||
is = new MarkEnforcingInputStream(is);
|
||||
is.mark(READ_LIMIT);
|
||||
@@ -49,7 +50,7 @@ class ImageSizeCalculator {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
if (size.error) {
|
||||
if (size.hasError()) {
|
||||
// need to mark again to re-add read limit
|
||||
is.mark(READ_LIMIT);
|
||||
try {
|
||||
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -232,7 +232,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -277,7 +277,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -296,7 +296,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -318,7 +318,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -398,7 +398,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class BlogPostFragment extends BasePostFragment implements BlogListener {
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
|
||||
@@ -156,7 +156,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -187,7 +187,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -242,7 +242,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -79,7 +79,7 @@ public class FeedPostFragment extends BasePostFragment {
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.messaging.AttachmentHeader;
|
||||
import org.briarproject.briar.api.attachment.AttachmentHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -101,7 +101,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(exception);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -128,7 +128,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
handleException(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,14 +3,12 @@ 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>>
|
||||
@@ -47,15 +45,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -2,22 +2,26 @@ 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.NotThreadSafe;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@NotThreadSafe
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class ContactItem {
|
||||
|
||||
private final Contact contact;
|
||||
private boolean connected;
|
||||
private final AuthorInfo authorInfo;
|
||||
private final boolean connected;
|
||||
|
||||
public ContactItem(Contact contact) {
|
||||
this(contact, false);
|
||||
public ContactItem(Contact contact, AuthorInfo authorInfo) {
|
||||
this(contact, authorInfo, false);
|
||||
}
|
||||
|
||||
public ContactItem(Contact contact, boolean connected) {
|
||||
public ContactItem(Contact contact, AuthorInfo authorInfo,
|
||||
boolean connected) {
|
||||
this.contact = contact;
|
||||
this.authorInfo = authorInfo;
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
@@ -25,12 +29,12 @@ public class ContactItem {
|
||||
return contact;
|
||||
}
|
||||
|
||||
public AuthorInfo getAuthorInfo() {
|
||||
return authorInfo;
|
||||
}
|
||||
|
||||
boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
void setConnected(boolean connected) {
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -14,9 +13,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
|
||||
@@ -40,9 +39,7 @@ public class ContactItemViewHolder<I extends ContactItem>
|
||||
}
|
||||
|
||||
protected void bind(I item, @Nullable OnContactClickListener<I> listener) {
|
||||
Author author = item.getContact().getAuthor();
|
||||
avatar.setImageDrawable(
|
||||
new IdenticonDrawable(author.getId().getBytes()));
|
||||
setAvatar(avatar, item);
|
||||
name.setText(getContactDisplayName(item.getContact()));
|
||||
|
||||
if (bulb != null) {
|
||||
|
||||
@@ -1,50 +1,73 @@
|
||||
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
|
||||
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
|
||||
ListAdapter<ContactListItem, ContactListItemViewHolder> {
|
||||
|
||||
public ContactListAdapter(Context context,
|
||||
// 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(
|
||||
OnContactClickListener<ContactListItem> listener) {
|
||||
super(context, ContactListItem.class, 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());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
|
||||
int i) {
|
||||
int viewType) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
||||
R.layout.list_item_contact, viewGroup, false);
|
||||
|
||||
return new ContactListItemViewHolder(v);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
return c1.isConnected() == c2.isConnected();
|
||||
public void onBindViewHolder(ContactListItemViewHolder viewHolder,
|
||||
int position) {
|
||||
viewHolder.bind(getItem(position), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(ContactListItem c1, ContactListItem c2) {
|
||||
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,23 +10,9 @@ 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;
|
||||
@@ -37,55 +23,35 @@ 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.core.app.ActivityCompat;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
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 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.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
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 EventListener,
|
||||
OnMenuItemClickListener {
|
||||
public class ContactListFragment extends BaseFragment
|
||||
implements OnMenuItemClickListener,
|
||||
OnContactClickListener<ContactListItem> {
|
||||
|
||||
public static final String TAG = ContactListFragment.class.getName();
|
||||
private static final Logger LOG = Logger.getLogger(TAG);
|
||||
|
||||
@Inject
|
||||
ConnectionRegistry connectionRegistry;
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private ContactListAdapter adapter;
|
||||
private ContactListViewModel viewModel;
|
||||
private final ContactListAdapter adapter = new ContactListAdapter(this);
|
||||
private BriarRecyclerView list;
|
||||
|
||||
/**
|
||||
* The Snackbar is non-null when shown and null otherwise.
|
||||
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
|
||||
@@ -93,12 +59,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
@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();
|
||||
@@ -114,6 +74,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(ContactListViewModel.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -129,37 +91,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
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);
|
||||
@@ -167,9 +98,30 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
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) {
|
||||
@@ -188,131 +140,20 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
eventBus.addListener(this);
|
||||
notificationManager.clearAllContactNotifications();
|
||||
notificationManager.clearAllContactAddedNotifications();
|
||||
loadContacts();
|
||||
checkForPendingContacts();
|
||||
viewModel.clearAllContactNotifications();
|
||||
viewModel.clearAllContactAddedNotifications();
|
||||
viewModel.loadContacts();
|
||||
viewModel.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;
|
||||
@@ -335,5 +176,4 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
Intent i = new Intent(getContext(), PendingContactListActivity.class);
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,31 +2,58 @@ 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.NotThreadSafe;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@NotThreadSafe
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class ContactListItem extends ContactItem {
|
||||
public class ContactListItem extends ContactItem
|
||||
implements Comparable<ContactListItem> {
|
||||
|
||||
private boolean empty;
|
||||
private long timestamp;
|
||||
private int unread;
|
||||
private final boolean empty;
|
||||
private final long timestamp;
|
||||
private final int unread;
|
||||
|
||||
public ContactListItem(Contact contact, boolean connected,
|
||||
GroupCount count) {
|
||||
super(contact, connected);
|
||||
public ContactListItem(Contact contact, AuthorInfo authorInfo,
|
||||
boolean connected, GroupCount count) {
|
||||
super(contact, authorInfo, connected);
|
||||
this.empty = count.getMsgCount() == 0;
|
||||
this.unread = count.getUnreadCount();
|
||||
this.timestamp = count.getLatestMsgTime();
|
||||
}
|
||||
|
||||
void addMessage(ConversationMessageHeader h) {
|
||||
empty = false;
|
||||
if (h.getTimestamp() > timestamp) timestamp = h.getTimestamp();
|
||||
if (!h.isRead()) unread++;
|
||||
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);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
@@ -41,4 +68,8 @@ public class ContactListItem extends ContactItem {
|
||||
return unread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ContactListItem o) {
|
||||
return Long.compare(o.getTimestamp(), timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ 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;
|
||||
|
||||
@@ -15,7 +13,6 @@ 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
|
||||
@@ -39,7 +36,8 @@ 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);
|
||||
@@ -52,10 +50,6 @@ 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.briarproject.briar.android.contact;
|
||||
|
||||
import dagger.Module;
|
||||
|
||||
@Module
|
||||
public class ContactModule {
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user