Compare commits

..

1 Commits

Author SHA1 Message Date
akwizgran
0703548e97 Use ACTION_GET_CONTENT to show more content providers. 2021-03-10 16:05:58 +00:00
515 changed files with 7293 additions and 19777 deletions

1
.gitignore vendored
View File

@@ -18,7 +18,6 @@ local.properties
# Android Studio
.idea/*
!.idea/inspectionProfiles/
!.idea/runConfigurations/
!.idea/codeStyleSettings.xml
!.idea/codeStyles

View File

@@ -1,70 +1,30 @@
image: briar/ci-image-android:latest
stages:
- test
- optional_tests
- check_reproducibility
- test
- optional_tests
- check_reproducibility
.base-test:
test:
stage: test
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources
after_script:
# these file change every time and should not be cached
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
test:
extends: .base-test
stage: test
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- when: always
android test:
extends: .base-test
stage: optional_tests
image: briar/ci-image-android-emulator:latest
script:
# start emulator first, so it can fail early
- start-emulator.sh
# run normal and screenshot tests together (exclude Large tests)
- ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.package=org.briarproject.briar.android -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest
after_script:
- adb pull /sdcard/Pictures/screenshots
artifacts:
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
paths:
- kernel.log
- logcat.txt
- briar-android/build/reports/androidTests/connected/flavors/*
- screenshots
expire_in: 3 days
when: on_failure
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- briar-android/**/*
when: manual
allow_failure: true
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true
retry:
max: 1
tags:
- kvm
test_reproducible:
stage: check_reproducibility
@@ -80,7 +40,6 @@ test_reproducible:
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
@@ -93,15 +52,11 @@ test_reproducible:
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
bridge test:
manual_tests:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
allow_failure: true
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true
when: manual
except:
- tags
pre_release_tests:
extends: .optional_tests

View File

@@ -7,7 +7,6 @@
<w>encrypter</w>
<w>identicon</w>
<w>introducee</w>
<w>introducees</w>
<w>introducer</w>
<w>onboarding</w>
</words>

View File

@@ -1,14 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreObjectMethods" value="true" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
<inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
<option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="true" />
<option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="true" />
<option name="SUGGEST_PRIVATE_FOR_INNERS" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,51 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Instrumentation Tests (destroys DB)" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="briar.briar-android" />
<option name="TESTING_TYPE" value="1" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="org.briarproject.briar.android" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="-e notAnnotation androidx.test.filters.LargeTest" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Hybrid>
<Java />
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -8,15 +8,11 @@ android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
packagingOptions {
doNotStrip '**/*.so'
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10302
versionName "1.3.2"
versionCode 10216
versionName "1.2.16"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -175,11 +175,6 @@ class AndroidBluetoothPlugin
} catch (IOException e) {
IoUtils.tryToClose(s, LOG, WARNING);
throw e;
} catch (NullPointerException e) {
// BluetoothSocket#connect() may throw an NPE under unknown
// circumstances
IoUtils.tryToClose(s, LOG, WARNING);
throw new IOException(e);
}
}

View File

@@ -114,8 +114,12 @@ public class AndroidUtils {
/**
* Returns an array of supported content types for image attachments.
* GIFs can't be compressed on API < 24 so they're not supported.
* <p>
* TODO: Remove this restriction when large message support is added
*/
public static String[] getSupportedImageContentTypes() {
return new String[] {"image/jpeg", "image/png", "image/gif"};
if (SDK_INT < 24) return new String[] {"image/jpeg", "image/png"};
else return new String[] {"image/jpeg", "image/png", "image/gif"};
}
}

View File

@@ -9,7 +9,4 @@ public interface FeatureFlags {
boolean shouldEnableProfilePictures();
boolean shouldEnableDisappearingMessages();
boolean shouldEnableConnectViaBluetooth();
}

View File

@@ -1,29 +0,0 @@
package org.briarproject.bramble.api.cleanup;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import java.util.Collection;
/**
* An interface for registering a hook with the {@link CleanupManager}
* that will be called when a message's cleanup deadline is reached.
*/
@NotNullByDefault
public interface CleanupHook {
/**
* Called when the cleanup deadlines of one or more messages are reached.
* <p>
* The callee is not required to delete the messages, but the hook won't be
* called again for these messages unless another cleanup timer is set (see
* {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId, long)}
* and {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)}).
*/
void deleteMessages(Transaction txn, GroupId g,
Collection<MessageId> messageIds) throws DbException;
}

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.api.cleanup;
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.MessageId;
/**
* The CleanupManager is responsible for tracking the cleanup deadlines of
* messages and passing them to their respective
* {@link CleanupHook CleanupHooks} when the deadlines are reached.
* <p>
* The CleanupManager responds to
* {@link CleanupTimerStartedEvent CleanupTimerStartedEvents} broadcast by the
* {@link DatabaseComponent}.
* <p>
* See {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId, long)},
* {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)},
* {@link DatabaseComponent#stopCleanupTimer(Transaction, MessageId)}.
*/
@NotNullByDefault
public interface CleanupManager {
/**
* When scheduling a cleanup task we overshoot the deadline by this many
* milliseconds to reduce the number of tasks that need to be scheduled
* when messages have cleanup deadlines that are close together.
*/
long BATCH_DELAY_MS = 1000;
/**
* Registers a hook to be called when messages are due for cleanup.
* This method should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/
void registerCleanupHook(ClientId c, int majorVersion,
CleanupHook hook);
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.bramble.api.cleanup.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a message's cleanup timer is started.
*/
@Immutable
@NotNullByDefault
public class CleanupTimerStartedEvent extends Event {
private final MessageId messageId;
private final long cleanupDeadline;
public CleanupTimerStartedEvent(MessageId messageId,
long cleanupDeadline) {
this.messageId = messageId;
this.cleanupDeadline = cleanupDeadline;
}
public MessageId getMessageId() {
return messageId;
}
public long getCleanupDeadline() {
return cleanupDeadline;
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.client;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
@@ -17,7 +16,6 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.Map;
@NotNullByDefault
@@ -52,11 +50,9 @@ public interface ClientHelper {
BdfDictionary getGroupMetadataAsDictionary(Transaction txn, GroupId g)
throws DbException, FormatException;
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
BdfDictionary query) throws DbException, FormatException;
BdfDictionary getMessageMetadataAsDictionary(MessageId m)
throws DbException, FormatException;
throws DbException,
FormatException;
BdfDictionary getMessageMetadataAsDictionary(Transaction txn, MessageId m)
throws DbException, FormatException;
@@ -123,17 +119,4 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
BdfDictionary properties) throws FormatException;
/**
* Retrieves the contact ID from the group metadata of the given contact
* group.
*/
ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException;
/**
* Stores the given contact ID in the group metadata of the given contact
* group.
*/
void setContactId(Transaction txn, GroupId contactGroupId, ContactId c)
throws DbException;
}

View File

@@ -1,9 +0,0 @@
package org.briarproject.bramble.api.client;
public interface ContactGroupConstants {
/**
* Group metadata key for associating a contact ID with a contact group.
*/
String GROUP_KEY_CONTACT_ID = "contactId";
}

View File

@@ -1,35 +0,0 @@
package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when the alias for a contact changed.
*/
@Immutable
@NotNullByDefault
public class ContactAliasChangedEvent extends Event {
private final ContactId contactId;
@Nullable
private final String alias;
public ContactAliasChangedEvent(ContactId contactId,
@Nullable String alias) {
this.contactId = contactId;
this.alias = alias;
}
public ContactId getContactId() {
return contactId;
}
@Nullable
public String getAlias() {
return alias;
}
}

View File

@@ -41,18 +41,6 @@ import javax.annotation.Nullable;
@NotNullByDefault
public interface DatabaseComponent extends TransactionManager {
/**
* Return value for {@link #getNextCleanupDeadline(Transaction)} if
* no messages are scheduled to be deleted.
*/
long NO_CLEANUP_DEADLINE = -1;
/**
* Return value for {@link #startCleanupTimer(Transaction, MessageId)}
* if the cleanup timer was not started.
*/
long TIMER_NOT_STARTED = -1;
/**
* Opens the database and returns true if the database already existed.
*
@@ -300,16 +288,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
throws DbException;
/**
* Returns the IDs of any delivered messages in the given group with
* metadata that matches all entries in the given query. If the query is
* empty, the IDs of all delivered messages are returned.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
Metadata query) throws DbException;
/**
* Returns the IDs of any messages that need to be validated.
* <p/>
@@ -336,15 +314,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<MessageId> getMessagesToShare(Transaction txn)
throws DbException;
/**
* Returns the IDs of any messages of any messages that are due for
* deletion, along with their group IDs.
* <p/>
* Read-only.
*/
Map<GroupId, Collection<MessageId>> getMessagesToDelete(Transaction txn)
throws DbException;
/**
* Returns the metadata for all delivered messages in the given group.
* <p/>
@@ -426,15 +395,6 @@ public interface DatabaseComponent extends TransactionManager {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE}
* if no messages are scheduled to be deleted.
* <p/>
* Read-only.
*/
long getNextCleanupDeadline(Transaction txn) throws DbException;
/*
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may
@@ -575,13 +535,6 @@ public interface DatabaseComponent extends TransactionManager {
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
throws DbException;
/**
* Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer.
*/
void setCleanupTimerDuration(Transaction txn, MessageId m, long duration)
throws DbException;
/**
* Marks the given contact as verified.
*/
@@ -604,12 +557,6 @@ public interface DatabaseComponent extends TransactionManager {
*/
void setMessagePermanent(Transaction txn, MessageId m) throws DbException;
/**
* Marks the given message as not shared. This method is only meant for
* testing.
*/
void setMessageNotShared(Transaction txn, MessageId m) throws DbException;
/**
* Marks the given message as shared.
*/
@@ -652,22 +599,6 @@ public interface DatabaseComponent extends TransactionManager {
void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
throws DbException;
/**
* Starts the cleanup timer for the given message, if a timer duration
* has been set and the timer has not already been started.
*
* @return The cleanup deadline, or {@link #TIMER_NOT_STARTED} if no
* timer duration has been set for this message or its timer has already
* been started.
*/
long startCleanupTimer(Transaction txn, MessageId m) throws DbException;
/**
* Stops the cleanup timer for the given message, if the timer has been
* started.
*/
void stopCleanupTimer(Transaction txn, MessageId m) throws DbException;
/**
* Stores the given transport keys, deleting any keys they have replaced.
*/

View File

@@ -57,7 +57,6 @@ public class Author implements Nameable {
/**
* Returns the author's name.
*/
@Override
public String getName() {
return name;
}

View File

@@ -21,6 +21,5 @@ public interface ReportingConstants {
* Hidden service address for reporting crashes and feedback to the
* developers.
*/
String DEV_ONION_ADDRESS =
"b2nkt5doeamvdmjzfz7g42hk5vdtlnktlgzhel2bgjcc4v4jhnx2qrqd.onion";
String DEV_ONION_ADDRESS = "cwqmubyvnig3wag3.onion";
}

View File

@@ -6,9 +6,7 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ValidationUtils {
@@ -66,9 +64,4 @@ public class ValidationUtils {
if (dictionary != null && dictionary.size() != size)
throw new FormatException();
}
public static void checkRange(@Nullable Long l, long min, long max)
throws FormatException {
if (l != null && (l < min || l > max)) throw new FormatException();
}
}

View File

@@ -1,8 +0,0 @@
package org.briarproject.bramble.test;
public interface TimeTravel {
void setCurrentTimeMillis(long now) throws InterruptedException;
void addCurrentTimeMillis(long add) throws InterruptedException;
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble;
import org.briarproject.bramble.cleanup.CleanupModule;
import org.briarproject.bramble.contact.ContactModule;
import org.briarproject.bramble.crypto.CryptoExecutorModule;
import org.briarproject.bramble.db.DatabaseExecutorModule;
@@ -15,8 +14,6 @@ import org.briarproject.bramble.versioning.VersioningModule;
public interface BrambleCoreEagerSingletons {
void inject(CleanupModule.EagerSingletons init);
void inject(ContactModule.EagerSingletons init);
void inject(CryptoExecutorModule.EagerSingletons init);
@@ -42,7 +39,6 @@ public interface BrambleCoreEagerSingletons {
class Helper {
public static void injectEagerSingletons(BrambleCoreEagerSingletons c) {
c.inject(new CleanupModule.EagerSingletons());
c.inject(new ContactModule.EagerSingletons());
c.inject(new CryptoExecutorModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons());

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble;
import org.briarproject.bramble.cleanup.CleanupModule;
import org.briarproject.bramble.client.ClientModule;
import org.briarproject.bramble.connection.ConnectionModule;
import org.briarproject.bramble.contact.ContactModule;
@@ -22,14 +21,15 @@ import org.briarproject.bramble.rendezvous.RendezvousModule;
import org.briarproject.bramble.settings.SettingsModule;
import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule;
import dagger.Module;
@Module(includes = {
CleanupModule.class,
ClientModule.class,
ClockModule.class,
ConnectionModule.class,
ContactModule.class,
CryptoModule.class,

View File

@@ -1,159 +0,0 @@
package org.briarproject.bramble.cleanup;
import org.briarproject.bramble.api.cleanup.CleanupHook;
import org.briarproject.bramble.api.cleanup.CleanupManager;
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;
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.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.lang.Math.max;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
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.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class CleanupManagerImpl implements CleanupManager, Service, EventListener {
private static final Logger LOG =
getLogger(CleanupManagerImpl.class.getName());
private final Executor dbExecutor;
private final DatabaseComponent db;
private final TaskScheduler taskScheduler;
private final Clock clock;
private final Map<ClientMajorVersion, CleanupHook> hooks =
new ConcurrentHashMap<>();
private final Object lock = new Object();
@GuardedBy("lock")
private final Set<CleanupTask> pending = new HashSet<>();
@Inject
CleanupManagerImpl(@DatabaseExecutor Executor dbExecutor,
DatabaseComponent db, TaskScheduler taskScheduler, Clock clock) {
this.dbExecutor = dbExecutor;
this.db = db;
this.taskScheduler = taskScheduler;
this.clock = clock;
}
@Override
public void registerCleanupHook(ClientId c, int majorVersion,
CleanupHook hook) {
hooks.put(new ClientMajorVersion(c, majorVersion), hook);
}
@Override
public void startService() {
maybeScheduleTask(clock.currentTimeMillis());
}
@Override
public void stopService() {
}
@Override
public void eventOccurred(Event e) {
if (e instanceof CleanupTimerStartedEvent) {
CleanupTimerStartedEvent a = (CleanupTimerStartedEvent) e;
maybeScheduleTask(a.getCleanupDeadline());
}
}
private void maybeScheduleTask(long deadline) {
synchronized (lock) {
for (CleanupTask task : pending) {
if (task.deadline <= deadline) return;
}
CleanupTask task = new CleanupTask(deadline);
pending.add(task);
scheduleTask(task);
}
}
private void scheduleTask(CleanupTask task) {
long now = clock.currentTimeMillis();
long delay = max(0, task.deadline - now + BATCH_DELAY_MS);
if (LOG.isLoggable(INFO)) {
LOG.info("Scheduling cleanup task in " + delay + " ms");
}
taskScheduler.schedule(() -> deleteMessagesAndScheduleNextTask(task),
dbExecutor, delay, MILLISECONDS);
}
private void deleteMessagesAndScheduleNextTask(CleanupTask task) {
try {
synchronized (lock) {
pending.remove(task);
}
long deadline = db.transactionWithResult(false, txn -> {
deleteMessages(txn);
return db.getNextCleanupDeadline(txn);
});
if (deadline != NO_CLEANUP_DEADLINE) {
maybeScheduleTask(deadline);
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private void deleteMessages(Transaction txn) throws DbException {
Map<GroupId, Collection<MessageId>> ids = db.getMessagesToDelete(txn);
for (Entry<GroupId, Collection<MessageId>> e : ids.entrySet()) {
GroupId groupId = e.getKey();
Collection<MessageId> messageIds = e.getValue();
if (LOG.isLoggable(INFO)) {
LOG.info(messageIds.size() + " messages to delete");
}
for (MessageId m : messageIds) db.stopCleanupTimer(txn, m);
Group group = db.getGroup(txn, groupId);
ClientMajorVersion cv = new ClientMajorVersion(group.getClientId(),
group.getMajorVersion());
CleanupHook hook = hooks.get(cv);
if (hook == null) {
throw new IllegalStateException("No cleanup hook for " + cv);
}
hook.deleteMessages(txn, groupId, messageIds);
}
}
private static class CleanupTask {
private final long deadline;
private CleanupTask(long deadline) {
this.deadline = deadline;
}
}
}

View File

@@ -1,29 +0,0 @@
package org.briarproject.bramble.cleanup;
import org.briarproject.bramble.api.cleanup.CleanupManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class CleanupModule {
public static class EagerSingletons {
@Inject
CleanupManager cleanupManager;
}
@Provides
@Singleton
CleanupManager provideCleanupManager(LifecycleManager lifecycleManager,
EventBus eventBus, CleanupManagerImpl cleanupManager) {
lifecycleManager.registerService(cleanupManager);
eventBus.addListener(cleanupManager);
return cleanupManager;
}
}

View File

@@ -2,13 +2,11 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -34,7 +32,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -42,7 +39,6 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -155,12 +151,6 @@ class ClientHelperImpl implements ClientHelper {
return metadataParser.parse(metadata);
}
@Override
public Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
BdfDictionary query) throws DbException, FormatException {
return db.getMessageIds(txn, g, metadataEncoder.encode(query));
}
@Override
public BdfDictionary getMessageMetadataAsDictionary(MessageId m)
throws DbException, FormatException {
@@ -399,27 +389,4 @@ class ClientHelperImpl implements ClientHelper {
return tpMap;
}
@Override
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException {
try {
BdfDictionary meta =
getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
}
@Override
public void setContactId(Transaction txn, GroupId contactGroupId,
ContactId c) throws DbException {
BdfDictionary meta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, c.getInt()));
try {
mergeGroupMetadata(txn, contactGroupId, meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -68,13 +68,6 @@ interface Database<T> {
*/
void close() throws DbException;
/**
* Returns true if the dirty flag was set while opening the database,
* indicating that the database has not been shut down properly the last
* time it was closed and some data could be lost.
*/
boolean wasDirtyOnInitialisation();
/**
* Starts a new transaction and returns an object representing it.
*/
@@ -504,25 +497,6 @@ interface Database<T> {
*/
Collection<MessageId> getMessagesToShare(T txn) throws DbException;
/**
* Returns the IDs of any messages of any messages that are due for
* deletion, along with their group IDs.
* <p/>
* Read-only.
*/
Map<GroupId, Collection<MessageId>> getMessagesToDelete(T txn)
throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be deleted, or
* {@link DatabaseComponent#NO_CLEANUP_DEADLINE} if no messages are
* scheduled to be deleted.
* <p/>
* Read-only.
*/
long getNextCleanupDeadline(T txn) throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may
@@ -632,10 +606,8 @@ interface Database<T> {
/**
* Marks a message as having been seen by the given contact.
*
* @return True if the message was not already marked as seen.
*/
boolean raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException;
void raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException;
/**
* Removes a contact from the database.
@@ -699,13 +671,6 @@ interface Database<T> {
*/
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
/**
* Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer.
*/
void setCleanupTimerDuration(T txn, MessageId m, long duration)
throws DbException;
/**
* Marks the given contact as verified.
*/
@@ -736,10 +701,9 @@ interface Database<T> {
void setMessagePermanent(T txn, MessageId m) throws DbException;
/**
* Marks the given message as shared or not.
* Marks the given message as shared.
*/
void setMessageShared(T txn, MessageId m, boolean shared)
throws DbException;
void setMessageShared(T txn, MessageId m) throws DbException;
/**
* Sets the validation and delivery state of the given message.
@@ -766,22 +730,6 @@ interface Database<T> {
void setTransportKeysActive(T txn, TransportId t, KeySetId k)
throws DbException;
/**
* Starts the cleanup timer for the given message, if a timer duration
* has been set and the timer has not already been started.
*
* @return The cleanup deadline, or
* {@link DatabaseComponent#TIMER_NOT_STARTED} if no timer duration has
* been set for this message or its timer has already been started.
*/
long startCleanupTimer(T txn, MessageId m) throws DbException;
/**
* Stops the cleanup timer for the given message, if the timer has been
* started.
*/
void stopCleanupTimer(T txn, MessageId m) throws DbException;
/**
* Updates the transmission count, expiry time and estimated time of arrival
* of the given message with respect to the given contact, using the latency

View File

@@ -1,12 +1,10 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactAliasChangedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
@@ -578,15 +576,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageIds(txn, g);
}
@Override
public Collection<MessageId> getMessageIds(Transaction transaction,
GroupId g, Metadata query) throws DbException {
T txn = unbox(transaction);
if (!db.containsGroup(txn, g))
throw new NoSuchGroupException();
return db.getMessageIds(txn, g, query);
}
@Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException {
@@ -608,13 +597,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessagesToShare(txn);
}
@Override
public Map<GroupId, Collection<MessageId>> getMessagesToDelete(
Transaction transaction) throws DbException {
T txn = unbox(transaction);
return db.getMessagesToDelete(txn);
}
@Override
public Map<MessageId, Metadata> getMessageMetadata(Transaction transaction,
GroupId g) throws DbException {
@@ -710,13 +692,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageDependents(txn, m);
}
@Override
public long getNextCleanupDeadline(Transaction transaction)
throws DbException {
T txn = unbox(transaction);
return db.getNextCleanupDeadline(txn);
}
@Override
public long getNextSendTime(Transaction transaction, ContactId c)
throws DbException {
@@ -820,17 +795,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
Collection<MessageId> acked = new ArrayList<>();
for (MessageId m : a.getMessageIds()) {
if (db.containsVisibleMessage(txn, c, m)) {
if (db.raiseSeenFlag(txn, c, m)) {
// This is the first time the message has been acked by
// this contact. Start the cleanup timer (a no-op unless
// a cleanup deadline has been set for this message)
long deadline = db.startCleanupTimer(txn, m);
if (deadline != TIMER_NOT_STARTED) {
transaction.attach(new CleanupTimerStartedEvent(m,
deadline));
}
acked.add(m);
}
db.raiseSeenFlag(txn, c, m);
acked.add(m);
}
}
if (acked.size() > 0) {
@@ -986,16 +952,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.removeTransportKeys(txn, t, k);
}
@Override
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
long duration) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.setCleanupTimerDuration(txn, m, duration);
}
@Override
public void setContactVerified(Transaction transaction, ContactId c)
throws DbException {
@@ -1014,7 +970,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
transaction.attach(new ContactAliasChangedEvent(c, alias));
db.setContactAlias(txn, c, alias);
}
@@ -1046,16 +1001,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.setMessagePermanent(txn, m);
}
@Override
public void setMessageNotShared(Transaction transaction, MessageId m)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.setMessageShared(txn, m, false);
}
@Override
public void setMessageShared(Transaction transaction, MessageId m)
throws DbException {
@@ -1065,7 +1010,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchMessageException();
if (db.getMessageState(txn, m) != DELIVERED)
throw new IllegalArgumentException("Shared undelivered message");
db.setMessageShared(txn, m, true);
db.setMessageShared(txn, m);
transaction.attach(new MessageSharedEvent(m));
}
@@ -1137,30 +1082,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.setTransportKeysActive(txn, t, k);
}
@Override
public long startCleanupTimer(Transaction transaction, MessageId m)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
long deadline = db.startCleanupTimer(txn, m);
if (deadline != TIMER_NOT_STARTED) {
transaction.attach(new CleanupTimerStartedEvent(m, deadline));
}
return deadline;
}
@Override
public void stopCleanupTimer(Transaction transaction, MessageId m)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.stopCleanupTimer(txn, m);
}
@Override
public void updateTransportKeys(Transaction transaction,
Collection<TransportKeySet> keys) throws DbException {

View File

@@ -37,10 +37,4 @@ interface DatabaseConstants {
* has passed since the last compaction.
*/
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
/**
* The {@link Settings} key under which the flag is stored indicating
* whether the database is marked as dirty.
*/
String DIRTY_KEY = "dirty";
}

View File

@@ -84,14 +84,9 @@ class H2Database extends JdbcDatabase {
@Override
public void close() throws DbException {
// H2 will close the database when the last connection closes
Connection c = null;
try {
c = createConnection();
super.closeAllConnections();
setDirty(c, false);
c.close();
} catch (SQLException e) {
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}
}

View File

@@ -81,7 +81,6 @@ class HyperSqlDatabase extends JdbcDatabase {
try {
super.closeAllConnections();
c = createConnection();
setDirty(c, false);
s = c.createStatement();
s.executeQuery("SHUTDOWN");
s.close();

View File

@@ -72,8 +72,6 @@ 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.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@@ -83,7 +81,6 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERE
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
@@ -101,7 +98,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing
static final int CODE_SCHEMA_VERSION = 48;
static final int CODE_SCHEMA_VERSION = 47;
// Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1;
@@ -183,11 +180,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " state INT NOT NULL,"
+ " shared BOOLEAN NOT NULL,"
+ " temporary BOOLEAN NOT NULL,"
// Null if no timer duration has been set
+ " cleanupTimerDuration BIGINT,"
// Null if no timer duration has been set or the timer
// hasn't started
+ " cleanupDeadline BIGINT,"
+ " length INT NOT NULL,"
+ " raw BLOB," // Null if message has been deleted
+ " PRIMARY KEY (messageId),"
@@ -344,10 +336,6 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
+ " ON statuses (contactId, timestamp)";
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
+ " ON messages (cleanupDeadline)";
private static final Logger LOG =
getLogger(JdbcDatabase.class.getName());
@@ -366,14 +354,9 @@ abstract class JdbcDatabase implements Database<Connection> {
@GuardedBy("connectionsLock")
private boolean closed = false;
private volatile boolean wasDirtyOnInitialisation = false;
protected abstract Connection createConnection()
throws DbException, SQLException;
// Used exclusively during open to compact the database after schema
// migrations or after DatabaseConstants#MAX_COMPACTION_INTERVAL_MS has
// elapsed
protected abstract void compactAndClose() throws DbException;
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
@@ -398,19 +381,13 @@ abstract class JdbcDatabase implements Database<Connection> {
try {
if (reopen) {
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
wasDirtyOnInitialisation = isDirty(s);
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
} else {
wasDirtyOnInitialisation = false;
createTables(txn);
initialiseSettings(txn);
compact = false;
}
if (LOG.isLoggable(INFO)) {
LOG.info("db dirty? " + wasDirtyOnInitialisation);
}
createIndexes(txn);
setDirty(txn, true);
commitTransaction(txn);
} catch (DbException e) {
abortTransaction(txn);
@@ -437,11 +414,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public boolean wasDirtyOnInitialisation() {
return wasDirtyOnInitialisation;
}
/**
* Compares the schema version stored in the database with the schema
* version used by the current code and applies any suitable migrations to
@@ -491,8 +463,7 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration43_44(dbTypes),
new Migration44_45(),
new Migration45_46(),
new Migration46_47(dbTypes),
new Migration47_48()
new Migration46_47(dbTypes)
);
}
@@ -517,16 +488,6 @@ abstract class JdbcDatabase implements Database<Connection> {
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private boolean isDirty(Settings s) {
return s.getBoolean(DIRTY_KEY, false);
}
protected void setDirty(Connection txn, boolean dirty) throws DbException {
Settings s = new Settings();
s.putBoolean(DIRTY_KEY, dirty);
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private void initialiseSettings(Connection txn) throws DbException {
Settings s = new Settings();
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
@@ -570,7 +531,6 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
s.close();
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
@@ -1330,9 +1290,7 @@ abstract class JdbcDatabase implements Database<Connection> {
public void deleteMessage(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE messages"
+ " SET raw = NULL, cleanupDeadline = NULL"
+ " WHERE messageId = ?";
String sql = "UPDATE messages SET raw = NULL WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
int affected = ps.executeUpdate();
@@ -1811,6 +1769,7 @@ abstract class JdbcDatabase implements Database<Connection> {
// Return early if there are no matches
if (intersection.isEmpty()) return Collections.emptySet();
}
if (intersection == null) throw new AssertionError();
return intersection;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
@@ -2267,39 +2226,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public Map<GroupId, Collection<MessageId>> getMessagesToDelete(
Connection txn) throws DbException {
long now = clock.currentTimeMillis();
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT messageId, groupId FROM messages"
+ " WHERE cleanupDeadline <= ?";
ps = txn.prepareStatement(sql);
ps.setLong(1, now);
rs = ps.executeQuery();
Map<GroupId, Collection<MessageId>> ids = new HashMap<>();
while (rs.next()) {
MessageId m = new MessageId(rs.getBytes(1));
GroupId g = new GroupId(rs.getBytes(2));
Collection<MessageId> messageIds = ids.get(g);
if (messageIds == null) {
messageIds = new ArrayList<>();
ids.put(g, messageIds);
}
messageIds.add(m);
}
rs.close();
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public long getNextSendTime(Connection txn, ContactId c)
throws DbException {
@@ -2330,31 +2256,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public long getNextCleanupDeadline(Connection txn) throws DbException {
Statement s = null;
ResultSet rs = null;
try {
String sql = "SELECT cleanupDeadline FROM messages"
+ " WHERE cleanupDeadline IS NOT NULL"
+ " ORDER BY cleanupDeadline LIMIT 1";
s = txn.createStatement();
rs = s.executeQuery(sql);
long nextDeadline = NO_CLEANUP_DEADLINE;
if (rs.next()) {
nextDeadline = rs.getLong(1);
if (rs.next()) throw new AssertionError();
}
rs.close();
s.close();
return nextDeadline;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public PendingContact getPendingContact(Connection txn, PendingContactId p)
throws DbException {
@@ -2875,7 +2776,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public boolean raiseSeenFlag(Connection txn, ContactId c, MessageId m)
public void raiseSeenFlag(Connection txn, ContactId c, MessageId m)
throws DbException {
PreparedStatement ps = null;
try {
@@ -2887,7 +2788,6 @@ abstract class JdbcDatabase implements Database<Connection> {
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
return affected == 1;
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
@@ -3121,25 +3021,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void setCleanupTimerDuration(Connection txn, MessageId m,
long duration) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE messages SET cleanupTimerDuration = ?"
+ " WHERE messageId = ? AND cleanupTimerDuration IS NULL";
ps = txn.prepareStatement(sql);
ps.setLong(1, duration);
ps.setBytes(2, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void setContactVerified(Connection txn, ContactId c)
throws DbException {
@@ -3247,24 +3128,22 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public void setMessageShared(Connection txn, MessageId m, boolean shared)
public void setMessageShared(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE messages SET shared = ?"
String sql = "UPDATE messages SET shared = TRUE"
+ " WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBoolean(1, shared);
ps.setBytes(2, m.getBytes());
ps.setBytes(1, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
// Update denormalised column in statuses
sql = "UPDATE statuses SET messageShared = ?"
sql = "UPDATE statuses SET messageShared = TRUE"
+ " WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBoolean(1, shared);
ps.setBytes(2, m.getBytes());
ps.setBytes(1, m.getBytes());
affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
@@ -3393,60 +3272,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public long startCleanupTimer(Connection txn, MessageId m)
throws DbException {
long now = clock.currentTimeMillis();
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "UPDATE messages"
+ " SET cleanupDeadline = ? + cleanupTimerDuration"
+ " WHERE messageId = ?"
+ " AND cleanupTimerDuration IS NOT NULL"
+ " AND cleanupDeadline IS NULL";
ps = txn.prepareStatement(sql);
ps.setLong(1, now);
ps.setBytes(2, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
if (affected == 0) return TIMER_NOT_STARTED;
sql = "SELECT cleanupDeadline FROM messages WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
long deadline = rs.getLong(1);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return deadline;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void stopCleanupTimer(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE messages SET cleanupDeadline = NULL"
+ " WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
int maxLatency) throws DbException {

View File

@@ -1,47 +0,0 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration47_48 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration47_48.class.getName());
@Override
public int getStartVersion() {
return 47;
}
@Override
public int getEndVersion() {
return 48;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
// Null if no timer duration has been set
s.execute("ALTER TABLE messages"
+ " ADD COLUMN cleanupTimerDuration BIGINT");
// Null if no timer duration has been set or the timer
// hasn't started
s.execute("ALTER TABLE messages"
+ " ADD COLUMN cleanupDeadline BIGINT");
s.execute("CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
+ " ON messages (cleanupDeadline)");
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -34,12 +34,12 @@ class KeyAgreementTransport {
Logger.getLogger(KeyAgreementTransport.class.getName());
// Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r ->
private static Predicate<Record> ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r ->
private static Predicate<Record> IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType());

View File

@@ -5,5 +5,6 @@ interface ClientVersioningConstants {
// Metadata keys
String MSG_KEY_UPDATE_VERSION = "version";
String MSG_KEY_LOCAL = "local";
String GROUP_KEY_CONTACT_ID = "contactId";
}

View File

@@ -50,6 +50,7 @@ import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@@ -160,7 +161,13 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId());
BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
// Create and store the first local update
List<ClientVersion> versions = new ArrayList<>(clients);
Collections.sort(versions);
@@ -222,7 +229,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, newRemoteStates);
// Call hooks for any visibilities that have changed
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
ContactId c = getContactId(txn, m.getGroupId());
if (!before.equals(after)) {
Contact contact = db.getContact(txn, c);
callVisibilityHooks(txn, contact, before, after);
@@ -514,6 +521,17 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
storeUpdate(txn, g, states, 1);
}
private ContactId getContactId(Transaction txn, GroupId g)
throws DbException {
try {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e);
}
}
private List<ClientState> updateStatesFromRemoteStates(
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
Set<ClientMajorVersion> remoteSet = new HashSet<>();

View File

@@ -1,12 +1,13 @@
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0

View File

@@ -1,18 +1,18 @@
package org.briarproject.bramble;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
public interface BrambleCoreIntegrationTestEagerSingletons
extends BrambleCoreEagerSingletons {
void inject(TimeTravelModule.EagerSingletons init);
void inject(DefaultTaskSchedulerModule.EagerSingletons init);
class Helper {
public static void injectEagerSingletons(
BrambleCoreIntegrationTestEagerSingletons c) {
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
c.inject(new TimeTravelModule.EagerSingletons());
c.inject(new DefaultTaskSchedulerModule.EagerSingletons());
}
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
@@ -70,8 +69,6 @@ import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -513,11 +510,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception {
context.checking(new Expectations() {{
// Check whether the group is in the DB (which it's not)
exactly(10).of(database).startTransaction();
exactly(8).of(database).startTransaction();
will(returnValue(txn));
exactly(10).of(database).containsGroup(txn, groupId);
exactly(8).of(database).containsGroup(txn, groupId);
will(returnValue(false));
exactly(10).of(database).abortTransaction(txn);
exactly(8).of(database).abortTransaction(txn);
// Allow other checks to pass
allowing(database).containsContact(txn, contactId);
will(returnValue(true));
@@ -526,7 +523,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
eventExecutor, shutdownManager);
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getGroup(transaction, groupId));
fail();
} catch (NoSuchGroupException expected) {
@@ -534,7 +531,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getGroupMetadata(transaction, groupId));
fail();
} catch (NoSuchGroupException expected) {
@@ -542,23 +539,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.getMessageIds(transaction, groupId));
fail();
} catch (NoSuchGroupException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.getMessageIds(transaction, groupId, new Metadata()));
fail();
} catch (NoSuchGroupException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessageMetadata(transaction, groupId));
fail();
} catch (NoSuchGroupException expected) {
@@ -566,7 +547,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessageMetadata(transaction, groupId,
new Metadata()));
fail();
@@ -575,7 +556,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessageStatus(transaction, contactId, groupId));
fail();
} catch (NoSuchGroupException expected) {
@@ -613,11 +594,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception {
context.checking(new Expectations() {{
// Check whether the message is in the DB (which it's not)
exactly(15).of(database).startTransaction();
exactly(12).of(database).startTransaction();
will(returnValue(txn));
exactly(15).of(database).containsMessage(txn, messageId);
exactly(12).of(database).containsMessage(txn, messageId);
will(returnValue(false));
exactly(15).of(database).abortTransaction(txn);
exactly(12).of(database).abortTransaction(txn);
// Allow other checks to pass
allowing(database).containsContact(txn, contactId);
will(returnValue(true));
@@ -642,7 +623,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessage(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
@@ -650,7 +631,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessageMetadata(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
@@ -658,7 +639,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessageState(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
@@ -666,7 +647,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessageStatus(transaction, contactId, messageId));
fail();
} catch (NoSuchMessageException expected) {
@@ -681,15 +662,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected
}
try {
db.transaction(false, transaction ->
db.setCleanupTimerDuration(transaction, message.getId(),
HOURS.toMillis(1)));
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.setMessagePermanent(transaction, message.getId()));
@@ -715,7 +687,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessageDependencies(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
@@ -723,28 +695,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(true, transaction ->
db.transaction(false, transaction ->
db.getMessageDependents(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.startCleanupTimer(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.stopCleanupTimer(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
}
@Test
@@ -1025,9 +981,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
oneOf(database).raiseSeenFlag(txn, contactId, messageId);
will(returnValue(true));
oneOf(database).startCleanupTimer(txn, messageId);
will(returnValue(TIMER_NOT_STARTED)); // No cleanup duration was set
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
}});
@@ -1040,56 +993,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
});
}
@Test
public void testReceiveDuplicateAck() throws Exception {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
oneOf(database).raiseSeenFlag(txn, contactId, messageId);
will(returnValue(false)); // Already acked
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
db.transaction(false, transaction -> {
Ack a = new Ack(singletonList(messageId));
db.receiveAck(transaction, contactId, a);
});
}
@Test
public void testReceiveAckWithCleanupTimer() throws Exception {
long deadline = System.currentTimeMillis();
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
oneOf(database).raiseSeenFlag(txn, contactId, messageId);
will(returnValue(true));
oneOf(database).startCleanupTimer(txn, messageId);
will(returnValue(deadline));
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(
CleanupTimerStartedEvent.class)));
oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
db.transaction(false, transaction -> {
Ack a = new Ack(singletonList(messageId));
db.receiveAck(transaction, contactId, a);
});
}
@Test
public void testReceiveMessage() throws Exception {
context.checking(new Expectations() {{

View File

@@ -41,12 +41,8 @@ import org.junit.Test;
import java.io.File;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -57,11 +53,10 @@ import java.util.concurrent.atomic.AtomicLong;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -356,7 +351,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
// Sharing the message should make it sendable
db.setMessageShared(txn, messageId, true);
db.setMessageShared(txn, messageId);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
@@ -636,7 +631,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// The group should not be visible to the contact
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
assertTrue(db.getGroupVisibility(txn, groupId).isEmpty());
assertEquals(emptyMap(),
db.getGroupVisibility(txn, groupId));
// Make the group visible to the contact
db.addGroupVisibility(txn, contactId, groupId, false);
@@ -659,7 +655,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Make the group invisible again
db.removeGroupVisibility(txn, contactId, groupId);
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
assertTrue(db.getGroupVisibility(txn, groupId).isEmpty());
assertEquals(emptyMap(),
db.getGroupVisibility(txn, groupId));
db.commitTransaction(txn);
db.close();
@@ -2043,7 +2040,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
// Share the message - now it should be sendable immediately
db.setMessageShared(txn, messageId, true);
db.setMessageShared(txn, messageId);
assertEquals(0, db.getNextSendTime(txn, contactId));
// Mark the message as requested - it should still be sendable
@@ -2350,148 +2347,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close();
}
@Test
public void testShutdownGracefully() throws Exception {
Database<Connection> db = open(false);
db.close();
open(true);
assertFalse(db.wasDirtyOnInitialisation());
}
@Test
public void testShutdownDirty() throws Exception {
Database<Connection> db = open(false);
// We want to simulate a dirty shutdown here which would normally be
// caused by an empty battery or by force closing the Android app.
// As there is no obvious way to simulate this, we're artificially
// causing an SqlException during close() here by unloading the JDBC
// drivers.
List<String> unloadedDrivers = unloadDrivers();
try {
db.close();
fail();
} catch (Exception e) {
// continue
e.printStackTrace();
}
// Reloading drivers to continue so that we're able to work with the
// database again.
reloadDrivers(unloadedDrivers);
db = open(true);
assertTrue(db.wasDirtyOnInitialisation());
}
@Test
public void testShutdownDirtyThenGracefully() throws Exception {
Database<Connection> db = open(false);
// Simulating a dirty shutdown here, look at #testShutdownDirty for
// details.
List<String> unloadedDrivers = unloadDrivers();
try {
db.close();
fail();
} catch (Exception e) {
// continue
}
reloadDrivers(unloadedDrivers);
db = open(true);
assertTrue(db.wasDirtyOnInitialisation());
db.close();
db = open(true);
assertFalse(db.wasDirtyOnInitialisation());
}
@Test
public void testCleanupTimer() throws Exception {
long duration = 60_000;
long now = System.currentTimeMillis();
AtomicLong time = new AtomicLong(now);
Database<Connection> db =
open(false, new TestMessageFactory(), new SettableClock(time));
Connection txn = db.startTransaction();
// No messages should be due or scheduled for deletion
assertTrue(db.getMessagesToDelete(txn).isEmpty());
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
// Add a group and a message
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, false, false, null);
// No messages should be due or scheduled for deletion
assertTrue(db.getMessagesToDelete(txn).isEmpty());
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
// Set the message's cleanup timer duration
db.setCleanupTimerDuration(txn, messageId, duration);
// No messages should be due or scheduled for deletion
assertTrue(db.getMessagesToDelete(txn).isEmpty());
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
// Start the message's cleanup timer
assertEquals(now + duration, db.startCleanupTimer(txn, messageId));
// The timer can't be started again
assertEquals(TIMER_NOT_STARTED, db.startCleanupTimer(txn, messageId));
// No messages should be due for deletion, but the message should be
// scheduled for deletion
assertTrue(db.getMessagesToDelete(txn).isEmpty());
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
// Stop the timer
db.stopCleanupTimer(txn, messageId);
// No messages should be due or scheduled for deletion
assertTrue(db.getMessagesToDelete(txn).isEmpty());
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
// Start the timer again
assertEquals(now + duration, db.startCleanupTimer(txn, messageId));
// No messages should be due for deletion, but the message should be
// scheduled for deletion
assertTrue(db.getMessagesToDelete(txn).isEmpty());
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
// 1 ms before the timer expires, no messages should be due for
// deletion but the message should be scheduled for deletion
time.set(now + duration - 1);
assertTrue(db.getMessagesToDelete(txn).isEmpty());
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
// When the timer expires, the message should be due and scheduled for
// deletion
time.set(now + duration);
assertEquals(singletonMap(groupId, singletonList(messageId)),
db.getMessagesToDelete(txn));
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
// 1 ms after the timer expires, the message should be due and
// scheduled for deletion
time.set(now + duration + 1);
assertEquals(singletonMap(groupId, singletonList(messageId)),
db.getMessagesToDelete(txn));
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
// Once the message has been deleted, it should no longer be due
// or scheduled for deletion
db.deleteMessage(txn, messageId);
assertTrue(db.getMessagesToDelete(txn).isEmpty());
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
}
private Database<Connection> open(boolean resume) throws Exception {
return open(resume, new TestMessageFactory(), new SystemClock());
}
@@ -2547,31 +2402,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
rootKey, alice);
}
private List<String> unloadDrivers() {
Enumeration<Driver> drivers = DriverManager.getDrivers();
List<String> unloaded = new ArrayList<>();
while (drivers.hasMoreElements()) {
Driver d = drivers.nextElement();
try {
DriverManager.deregisterDriver(d);
unloaded.add(d.getClass().getName());
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
return unloaded;
}
private void reloadDrivers(List<String> unloadedDrivers)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException, SQLException {
for (String driverName : unloadedDrivers) {
DriverManager.registerDriver(
(Driver) Class.forName(driverName).newInstance());
}
}
@After
public void tearDown() {
deleteTestDirectory(testDir);

View File

@@ -1,122 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.junit.Assert.fail;
/**
* A {@link TaskScheduler} for use in tests. The scheduler keeps all scheduled
* tasks in a queue until {@link #runTasks()} is called.
*/
@NotNullByDefault
class TestTaskScheduler implements TaskScheduler {
private final Queue<Task> queue = new PriorityBlockingQueue<>();
private final Clock clock;
TestTaskScheduler(Clock clock) {
this.clock = clock;
}
@Override
public Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit) {
AtomicBoolean cancelled = new AtomicBoolean(false);
return schedule(task, executor, delay, unit, cancelled);
}
@Override
public Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit) {
AtomicBoolean cancelled = new AtomicBoolean(false);
return scheduleWithFixedDelay(task, executor, delay, interval, unit,
cancelled);
}
private Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit, AtomicBoolean cancelled) {
long delayMillis = MILLISECONDS.convert(delay, unit);
long dueMillis = clock.currentTimeMillis() + delayMillis;
Task t = new Task(task, executor, dueMillis, cancelled);
queue.add(t);
return t;
}
private Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit, AtomicBoolean cancelled) {
// All executions of this periodic task share a cancelled flag
Runnable wrapped = () -> {
task.run();
scheduleWithFixedDelay(task, executor, interval, interval, unit,
cancelled);
};
return schedule(wrapped, executor, delay, unit, cancelled);
}
/**
* Runs any scheduled tasks that are due.
*/
void runTasks() throws InterruptedException {
long now = clock.currentTimeMillis();
while (true) {
Task t = queue.peek();
if (t == null || t.dueMillis > now) return;
t = queue.poll();
// Submit the task to its executor and wait for it to finish
if (!t.run().await(1, MINUTES)) fail();
}
}
private static class Task
implements Cancellable, Comparable<Task> {
private final Runnable task;
private final Executor executor;
private final long dueMillis;
private final AtomicBoolean cancelled;
private Task(Runnable task, Executor executor, long dueMillis,
AtomicBoolean cancelled) {
this.task = task;
this.executor = executor;
this.dueMillis = dueMillis;
this.cancelled = cancelled;
}
@SuppressWarnings("UseCompareMethod") // Animal Sniffer
@Override
public int compareTo(Task task) {
return Long.valueOf(dueMillis).compareTo(task.dueMillis);
}
/**
* Submits the task to its executor and returns a latch that will be
* released when the task finishes.
*/
public CountDownLatch run() {
if (cancelled.get()) return new CountDownLatch(0);
CountDownLatch latch = new CountDownLatch(1);
executor.execute(() -> {
task.run();
latch.countDown();
});
return latch;
}
@Override
public void cancel() {
cancelled.set(true);
}
}
}

View File

@@ -1,98 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.test.SettableClock;
import org.briarproject.bramble.test.TimeTravel;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class TimeTravelModule {
public static class EagerSingletons {
@Inject
TaskScheduler scheduler;
}
private final ScheduledExecutorService scheduledExecutorService;
private final Clock clock;
private final TaskScheduler taskScheduler;
private final TimeTravel timeTravel;
public TimeTravelModule() {
this(false);
}
public TimeTravelModule(boolean travel) {
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ScheduledThreadPoolExecutor.DiscardPolicy();
scheduledExecutorService =
new ScheduledThreadPoolExecutor(1, policy);
if (travel) {
// Use a SettableClock and TestTaskScheduler to allow time travel
AtomicLong time = new AtomicLong(System.currentTimeMillis());
clock = new SettableClock(time);
TestTaskScheduler testTaskScheduler = new TestTaskScheduler(clock);
taskScheduler = testTaskScheduler;
timeTravel = new TimeTravel() {
@Override
public void setCurrentTimeMillis(long now)
throws InterruptedException {
time.set(now);
testTaskScheduler.runTasks();
}
@Override
public void addCurrentTimeMillis(long add)
throws InterruptedException {
time.addAndGet(add);
testTaskScheduler.runTasks();
}
};
} else {
// Use the default clock and task scheduler
clock = new SystemClock();
taskScheduler = new TaskSchedulerImpl(scheduledExecutorService);
timeTravel = new TimeTravel() {
@Override
public void setCurrentTimeMillis(long now) {
throw new UnsupportedOperationException();
}
@Override
public void addCurrentTimeMillis(long add) {
throw new UnsupportedOperationException();
}
};
}
}
@Provides
Clock provideClock() {
return clock;
}
@Provides
@Singleton
TaskScheduler provideTaskScheduler(LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(scheduledExecutorService);
return taskScheduler;
}
@Provides
TimeTravel provideTimeTravel() {
return timeTravel;
}
}

View File

@@ -3,8 +3,8 @@ package org.briarproject.bramble.test;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
import org.briarproject.bramble.event.DefaultEventExecutorModule;
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
import org.briarproject.bramble.system.TimeTravelModule;
import dagger.Module;
import dagger.Provides;
@@ -12,11 +12,11 @@ import dagger.Provides;
@Module(includes = {
DefaultBatteryManagerModule.class,
DefaultEventExecutorModule.class,
DefaultTaskSchedulerModule.class,
DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class,
TestPluginConfigModule.class,
TestSecureRandomModule.class,
TimeTravelModule.class
TestSecureRandomModule.class
})
public class BrambleCoreIntegrationTestModule {
@@ -33,16 +33,6 @@ public class BrambleCoreIntegrationTestModule {
public boolean shouldEnableProfilePictures() {
return true;
}
@Override
public boolean shouldEnableDisappearingMessages() {
return true;
}
@Override
public boolean shouldEnableConnectViaBluetooth() {
return true;
}
};
}
}

View File

@@ -38,6 +38,7 @@ import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
import static org.junit.Assert.assertEquals;
@@ -59,6 +60,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final ClientId clientId = getClientId();
private final long now = System.currentTimeMillis();
private final Transaction txn = new Transaction(null, false);
private final BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
private ClientVersioningManagerImpl createInstance() {
context.checking(new Expectations() {{
@@ -120,8 +123,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId());
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
groupMeta);
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
@@ -457,8 +460,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// Get contact ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
// No states or visibilities have changed
}});
@@ -488,9 +492,10 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Get contact ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
// Get client ID
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
// No states or visibilities have changed
}});
@@ -541,6 +546,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
@@ -570,8 +577,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
@@ -611,6 +619,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
@@ -640,8 +650,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);

View File

@@ -16,7 +16,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'net.java.dev.jna:jna:4.5.2'
implementation 'net.java.dev.jna:jna-platform:4.5.2'
tor 'org.briarproject:tor:0.3.5.13-1@zip'
tor 'org.briarproject:tor:0.3.5.13@zip'
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -8,9 +8,9 @@ import org.briarproject.bramble.system.JavaSystemModule;
import dagger.Module;
@Module(includes = {
CircumventionModule.class,
JavaNetworkModule.class,
JavaSystemModule.class,
CircumventionModule.class,
SocksModule.class
})
public class BrambleJavaModule {

View File

@@ -25,7 +25,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.OsUtils.isLinux;
@@ -97,15 +96,8 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
String architecture = null;
if (isLinux()) {
String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) {
architecture = "linux-x86_64";
} else if (arch.equals("aarch64")) {
architecture = "linux-aarch64";
} else if (arch.equals("arm")) {
architecture = "linux-armhf";
}
}
if (architecture == null) {
@@ -113,10 +105,6 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
return null;
}
if (LOG.isLoggable(INFO)) {
LOG.info("The selected architecture for Tor is " + architecture);
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();

View File

@@ -24,7 +24,7 @@ dependencyVerification {
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
'org.briarproject:tor:0.3.5.13-1:tor-0.3.5.13-1.zip:ef35c16bf8dc1f4c75ed71d9f55e4514f383d124ec96b859aca647c990927c99',
'org.briarproject:tor:0.3.5.13:tor-0.3.5.13.zip:1c5f0b821ee2aadb0ea04aa96caab3ca0a08370cce8de81c2dfe04d172f8a2a0',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -10,6 +10,4 @@ src/main/res/values-iw
/fastlane/metadata/android/screenshots.html
/fastlane/metadata/android/*/images
/fastlane/report.xml
/fastlane/README.md
/fastlane/metadata/android/*/changelogs
/fastlane/README.md

View File

@@ -9,19 +9,3 @@ source_lang = en
type = ANDROID
minimum_perc = 80
[briar.google-play-short-description]
# https://support.google.com/googleplay/android-developer/answer/9844778?hl=en#zippy=%2Cview-list-of-available-languages
lang_map = en: en-US, de: de-DE, gl: gl-ES, tr: tr-TR, zh-Hans: zh-CN
file_filter = fastlane/metadata/android/<lang>/short_description.txt
source_file = fastlane/metadata/android/en-US/short_description.txt
source_lang = en
type = TXT
minimum_perc = 100
[briar.google-play-full-description]
lang_map = en: en-US, de: de-DE, gl: gl-ES, tr: tr-TR, zh-Hans: zh-CN
file_filter = fastlane/metadata/android/<lang>/full_description.txt
source_file = fastlane/metadata/android/en-US/full_description.txt
source_lang = en
type = TXT
minimum_perc = 100

View File

@@ -19,15 +19,11 @@ android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
packagingOptions {
doNotStrip '**/*.so'
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10302
versionName "1.3.2"
versionCode 10216
versionName "1.2.16"
applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true
@@ -80,7 +76,6 @@ android {
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
unitTests {
includeAndroidResources = true
}
@@ -149,8 +144,6 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestUtil 'androidx.test:orchestrator:1.3.0'
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation 'junit:junit:4.13.1'

View File

@@ -1,2 +1,2 @@
json_key_file(ENV["BRIAR_GOOGLE_PLAY_JSON_KEY_FILE"])
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("org.briarproject.briar.android")

View File

@@ -13,8 +13,6 @@
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
opt_out_usage
default_platform(:android)
platform :android do
@@ -26,21 +24,6 @@ platform :android do
system './demo-mode-deactivate.sh'
system './rename_screenshots.py'
end
desc "Updates Google Play metadata (title, descriptions, video, etc.)"
lane :metadata do
system './update-metadata.sh'
upload_to_play_store(
skip_upload_apk: true,
skip_upload_aab: true,
skip_upload_metadata: false,
skip_upload_changelogs: true,
skip_upload_images: true,
skip_upload_screenshots: true,
validate_only: false,
)
end
end

View File

@@ -1,5 +0,0 @@
El Briar és una aplicació de missatgeria dissenyada per a activistes, periodistes i qualsevol persona en general que necessiti una manera segura, fàcil i robsuta de comunicar-se. Al contrari que les aplicacions tradicionals, el Briar no depèn d'un servidor central: els missatges se sincronitzen directament entre els dispositius dels usuaris. Si Internet cau, el Briar pot continuar funcionant mitjançant Bluetooth o Wi-Fi, mantenint el flux fins i tot amb aquesta situació caòtica. Amb un funcionament normal d'Internet, el Briar es pot sincronitzar a través de la xarxa Tor, protegint els usuaris i les seves comunicacions d'ulls indiscrets.
L'aplicació té com a característica principal l'enviament de missatges privats en grups, fòrums o blocs, atès que es basa en la xarxa Tor. Qualsevol interacció al Briar s'emmagatzema només al vostre dispositiu fins que vulgueu compartir-ho amb altres usuaris.
No hi han anuncis ni seguiment El codi font d'aquesta aplicació està disponible per a tothom que vulgui inspeccionar-ho, com ja l'han auditat empreses professionals. Totes les versions del Briar són reproduïbles, la qual cosa permet verificar que el codi font publicat coincideix exactament amb l'aplicació publicada aquí. El desenvolupament és a càrrec d'un petit equip sense ànim lucratiu.

View File

@@ -1 +0,0 @@
Missatgeria segura, arreu.

View File

@@ -1 +0,0 @@
Briar

View File

@@ -1,5 +0,0 @@
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging apps, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping the information flowing in a crisis. If the internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
The app features private messages, groups and forums as well as blogs. Support for Tor network is built into the app. Everything you do in Briar is only stored on your device unless you decide to share it with other users.
There are no advertisements and no tracking. The source code of the app is completely open for anyone to inspect and has already been professionally audited. All releases of Briar are reproducible, making it possible to verify that the published source code matches exactly the app published here. Development is done by a small non-profit team.

View File

@@ -1 +0,0 @@
Secure messaging, anywhere.

View File

@@ -1 +0,0 @@
https://www.youtube.com/watch?v=ark7Y4u__SM

View File

@@ -1,5 +0,0 @@
Briar یک برنامه پیام رسان می باشد که برای فعالان، روزنامه نگاران و هر کسی که نیازمند یک راه امن، راحت و پیشرفته برای ارتباط با دیگران است می باشد. برخلاف برنامه‌ های پیام‌رسان‌ مرسوم، Briar به سرور متمرکز اتکا ندارد - پیام ها به صورت مستقیم بین دستگاه کاربران همگام می شود. اگر اینترنت کار نکند، Briar می‌تواند از طریق بلوتوث یا وای‌-فای همگام سازی کرده، جریان اطلاعات را در زمان بحران نگه دارد. اگر اینترنت کار کند، Briar می‌تواند برای محافظت کاربران و وابط آن ها از از شنود، از طریق شبکه تور همگام سازی کند.
این برنامه امکان ارسال پیام خصوصی، گروهی، تالارهای گفتمان و وبلاگ را در اختیار شما می‌گذارد. در این برنامه از شبکه‌ی تور پشتیبانی می‌شود. هر آنچه در برایر (Briar) انجام می‌دهید، تنها در دستگاه شما ذخیره می‌شود مگر اینکه خودتان آن را با دیگران به اشتراک بگذارید.
این برنامه هیچ تبلیغ و ردیابی از کاربران خود انجام نمی‌دهد. کد منبع برنامه به صورت کاملا باز در اختیار همه قرار دارد و کاملا حرفه‌ای بررسی شده است. تمامی نسخه‌های جدید برایر (Briar) قابل تولید مجدد هستند که امکان تائید یکسان بودن برنامه منتشر شده با کد منبع را می‌دهد. توسعه این برنامه توسط یک تیم غیرانتفاعی کوچک صورت می‌پذیرد.

View File

@@ -1 +0,0 @@
پیام‌رسان امن، در هر کجا

View File

@@ -1 +0,0 @@
Briar

View File

@@ -1 +0,0 @@
https://www.youtube.com/watch?v=ogDywPyPrB4

View File

@@ -1,5 +0,0 @@
Briar é unha app de mensaxería deseñada para activistas, xornalistas e calquera que precise comunicarse de xeito robusto, simple e seguro. Ao contrario que as apps de mensaxería tradicionais, Briar non depende dun servidor central - as mensaxes sincronízanse directamente entre os dispositivos das persoas usuarias. Se caese internet, Briar pode sincronizar a través de Bluetooth ou Wi-Fi, mantendo o fluxo de información en períodos de crise. Se hai internet, Briar pode sincronizar a través da rede Tor, protexendo ás usuarias e ás súas relacións da vixilancia.
A app proporciona mensaxes privadas, grupos e foros así como blogs. O soporte para a rede Tor está incluído na propia app. Todo o que fas en Briar só se garda no teu dispositivo a non ser que decidas compartilo con outras usuarias.
Non hai publicidade nen rastrexo da túa actividade. O código fonte da app está completamente aberto a calquera que queira revisalo e xa foi auditado profesionalmente. Todas as versións de Briar son reproducibles, facendo posible verificar que o código fonte publicado coincide exactamente coa app publicada aquí. O desenvolvemento realízase por un pequeno equipo de voluntarias.

View File

@@ -1 +0,0 @@
Mensaxería segura, en todo lugar.

View File

@@ -1,5 +0,0 @@
Briar yra žinučių perdavimo programėlė, skirta aktyvistams, žurnalistams ir visiems kitiems, kam reikia saugaus, lengvo ir veiksmingo bendravimo būdo. Kitaip nei kitos tradicinės žinučių perdavimo programėlės, Briar nepasikliauja centriniu serveriu - žinutės yra sinchronizuojamos tiesiogiai tarp naudotojų įrenginių. Jei nėra interneto, Briar gali sinchronizuotis per Bluetooth ar belaidį ryšį (Wi-Fi), kad palaikytų informacijos perdavimą kriziniais atvejais. Jei internetas veikia, Briar gali sinchronizuotis per Tor tinklą, apsaugodama naudotojus ir jų tarpusavio ryšius nuo stebėjimo.
Programėlėje yra galimybė naudotis privačiomis žinutėmis, grupėmis, forumais, o taip pat tinklaraščiais. Tor tinklo palaikymas yra įtaisytas į programėlę. Viskas, ką atliekate Briar programėlėje, yra laikoma jūsų įrenginyje, nebent nuspręsite pasidalinti tuo su kitais naudotojais.
Nėra jokių reklamų ir jokio sekimo. Pradinis programėlės kodas yra visiškai atviras kiekvienam, norinčiam jį ištirti, ir jau buvo profesionaliai patikrintas. Visos Briar laidos yra atgaminamos, todėl galima patikrinti ir įsitikinti, kad paskelbtas pradinis kodas tiksliai atitinka čia paskelbtą programėlę. Programėlės plėtojimą vykdo maža nepelno siekianti komanda.

View File

@@ -1 +0,0 @@
Saugus susirašinėjimas, bet kur.

View File

@@ -1 +0,0 @@
Briar

View File

@@ -1,5 +0,0 @@
Briar aktivistler ve gazeteciler başta olmak üzere güvenli, kolay ve sağlam bir iletişim isteyen herkes için tasarlanmış bir ileti sistemidir. Geleneksel ileti sistemlerinin aksine Briar merkezi bir sunucu kullanmaz, iletiler doğrudan kullanıcıların aygıtları arasında eşleştirilir. Briar, eğer İnternet yoksa Bluetooth veya Wi-Fi aracılığıyla da iletileri iletebilir, böylece kriz durumlarında bilgi akışını sürdürür. İnternet varken Briar Tor ağı aracılığıyla iletim sağlar, böylece kullanıcıları ve ilişkilerini gözetimden korumuş olur.
Bu uygulama özel iletiler, gruplar ve forumlarla birlikte blog özelliklerine sahiptir. Tor ağı desteği uygulamada gömülüdür. Briar üzerinde yaptığınız her şey, siz başka kullanıcılarla paylaşmayı seçmediğiniz sürece, sadece kendi aygıtınızda saklanır.
Ne reklam var, ne de sizi izleme. Uygulamanın kaynak kodu, incelemek isteyen herkese tamamen açıktır ve zaten profesyonel olarak da denetlenmiştir. Briar'ın tüm sürümleri yeniden üretilebilir, böylece yayınlanan kaynak kodun, burada yayınlanan uygulamayla tam olarak eşlendiğini doğrulamak mümkündür. Uygulama kâr amacı gütmeyen küçük bir ekip tarafından geliştirilmektedir.

View File

@@ -1 +0,0 @@
Güvenli mesajlaşma, nerede olursa olsun.

View File

@@ -1,5 +0,0 @@
Briar 是一款为活动人士、记者和其他需要安全、轻松和强大的通信方式的人设计的消息应用。与传统的消息应用不同Briar 不依赖中央服务器——消息在用户设备之间直接同步。即便互联网中断Briar 也可以通过蓝牙或 Wi-Fi 进行同步在危机中保持信息流通。如果互联网连接正常Briar 可以通过 Tor 网络进行同步,从而保护用户及其关系不受监视。
这款应用的功能包括私人信息、群组、论坛和博客。应用程序内置了对Tor网络的支持。你在Briar中所做的一切都只存储在你的设备上除非你决定与其他用户共享它。
没有广告也没有跟踪。该应用程序的源代码是完全开放的任何人都可以检查并且已经经过专业审计。Briar的所有版本都是可复制的这使得验证发布的源代码与这里发布的应用程序完全匹配成为可能。开发是由一个小型非营利团队完成的。

View File

@@ -1 +0,0 @@
安全收发信息,不论何处

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env bash
tx pull -a -r briar.google-play-full-description,briar.google-play-short-description
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
for LANG_DIR in "$DIR"/metadata/android/*; do
if [[ "$LANG_DIR" == *en-US ]]; then
continue
fi
if [[ -f "$LANG_DIR/full_description.txt" ]] && [[ -f "$LANG_DIR/short_description.txt" ]]; then
# every language uses the same app title
cp "$DIR/metadata/android/en-US/title.txt" "$LANG_DIR/title.txt"
echo "$LANG_DIR"
else
# not complete, remove
rm -r "$LANG_DIR"
fi
done

View File

@@ -19,9 +19,4 @@ public class BriarTestComponentApplication extends BriarApplicationImpl {
return component;
}
@Override
public boolean isInstrumentationTest() {
return true;
}
}

View File

@@ -1,65 +0,0 @@
package org.briarproject.briar.android;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.IOException;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicBoolean;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.FailureHandler;
import androidx.test.espresso.base.DefaultFailureHandler;
import androidx.test.runner.screenshot.BasicScreenCaptureProcessor;
import androidx.test.runner.screenshot.ScreenCapture;
import androidx.test.runner.screenshot.ScreenCaptureProcessor;
import androidx.test.runner.screenshot.Screenshot;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
@NotNullByDefault
public class ScreenshotOnFailureRule implements TestRule {
FailureHandler defaultFailureHandler =
new DefaultFailureHandler(getApplicationContext());
@Override
public Statement apply(Statement base, Description description) {
HashSet<ScreenCaptureProcessor> processors = new HashSet<>(1);
processors.add(new BasicScreenCaptureProcessor());
Screenshot.addScreenCaptureProcessors(processors);
return new Statement() {
@Override
public void evaluate() throws Throwable {
AtomicBoolean errorHandled = new AtomicBoolean(false);
Espresso.setFailureHandler((throwable, matcher) -> {
takeScreenshot(description);
errorHandled.set(true);
defaultFailureHandler.handle(throwable, matcher);
});
try {
base.evaluate();
} catch (Throwable t) {
if (!errorHandled.get()) {
takeScreenshot(description);
}
throw t;
}
}
};
}
private void takeScreenshot(Description description) {
String name = description.getTestClass().getSimpleName();
ScreenCapture capture = Screenshot.capture();
capture.setName(name);
try {
capture.process();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -6,25 +6,19 @@ import android.content.Intent;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.briar.R;
import org.junit.ClassRule;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
@SuppressWarnings("WeakerAccess")
public abstract class UiTest {
@ClassRule
public static final ScreenshotOnFailureRule screenshotOnFailureRule =
new ScreenshotOnFailureRule();
protected final String USERNAME =
getApplicationContext().getString(R.string.screenshot_alice);
protected static final String PASSWORD = "123456";
@@ -33,8 +27,6 @@ public abstract class UiTest {
protected AccountManager accountManager;
@Inject
protected LifecycleManager lifecycleManager;
@Inject
protected SettingsManager settingsManager;
public UiTest() {
BriarTestComponentApplication app = getApplicationContext();
@@ -43,18 +35,26 @@ public abstract class UiTest {
protected abstract void inject(BriarUiTestComponent component);
protected void startActivity(Class<? extends Activity> clazz) {
Intent i = new Intent(getApplicationContext(), clazz);
i.addFlags(FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(i);
}
@NotNullByDefault
protected class CleanAccountTestRule<A extends Activity>
extends IntentsTestRule<A> {
@Nullable
private final Runnable runnable;
public CleanAccountTestRule(Class<A> activityClass) {
super(activityClass);
this.runnable = null;
}
/**
* Use this if you need to run code before launching the activity.
* Note: You need to use {@link #launchActivity(Intent)} yourself
* to start the activity.
*/
public CleanAccountTestRule(Class<A> activityClass, Runnable runnable) {
super(activityClass, false, false);
this.runnable = runnable;
}
@Override
@@ -62,13 +62,16 @@ public abstract class UiTest {
super.beforeActivityLaunched();
accountManager.deleteAccount();
accountManager.createAccount(USERNAME, PASSWORD);
Intent serviceIntent =
new Intent(getApplicationContext(), BriarService.class);
getApplicationContext().startService(serviceIntent);
try {
lifecycleManager.waitForStartup();
} catch (InterruptedException e) {
throw new AssertionError(e);
if (runnable != null) {
Intent serviceIntent =
new Intent(getApplicationContext(), BriarService.class);
getApplicationContext().startService(serviceIntent);
try {
lifecycleManager.waitForStartup();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
runnable.run();
}
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android;
import android.app.Activity;
import android.util.Log;
import android.view.View;
import org.hamcrest.Matcher;
@@ -15,13 +14,9 @@ import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.util.HumanReadables.describe;
import static androidx.test.espresso.util.TreeIterables.breadthFirstViewTraversal;
import static androidx.test.runner.lifecycle.Stage.RESUMED;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -30,26 +25,13 @@ public class ViewActions {
private final static long TIMEOUT_MS = SECONDS.toMillis(10);
private final static long WAIT_MS = 50;
public static void waitFor(final Matcher<View> viewMatcher) {
onView(isRoot()).perform(waitUntilMatches(hasDescendant(viewMatcher)));
}
public static void waitFor(final Class<? extends Activity> clazz) {
onView(isRoot()).perform(waitForActivity(clazz, RESUMED, TIMEOUT_MS));
}
public static void waitFor(final Class<? extends Activity> clazz,
long timeout) {
onView(isRoot()).perform(waitForActivity(clazz, RESUMED, timeout));
}
public static ViewAction waitUntilMatches(Matcher<View> viewMatcher) {
return waitUntilMatches(viewMatcher, TIMEOUT_MS);
}
private static ViewAction waitUntilMatches(Matcher<View> viewMatcher,
long timeout) {
return new CustomViewAction(timeout) {
return new CustomViewAction() {
@Override
protected boolean exitConditionTrue(View view) {
for (View child : breadthFirstViewTraversal(view)) {
@@ -66,62 +48,24 @@ public class ViewActions {
};
}
public static ViewAction waitForActivity(Class<? extends Activity> clazz,
Stage stage, long timeout) {
return new CustomViewAction(timeout) {
public static ViewAction waitForActivity(Activity activity, Stage stage) {
return new CustomViewAction() {
@Override
protected boolean exitConditionTrue(View view) {
boolean found = false;
ActivityLifecycleMonitor lifecycleMonitor =
ActivityLifecycleMonitorRegistry.getInstance();
log(lifecycleMonitor);
for (Activity a : lifecycleMonitor
.getActivitiesInStage(stage)) {
if (a.getClass().equals(clazz)) found = true;
}
return found;
}
private void log(ActivityLifecycleMonitor lifecycleMonitor) {
log(lifecycleMonitor, Stage.PRE_ON_CREATE);
log(lifecycleMonitor, Stage.CREATED);
log(lifecycleMonitor, Stage.STARTED);
log(lifecycleMonitor, Stage.RESUMED);
log(lifecycleMonitor, Stage.PAUSED);
log(lifecycleMonitor, Stage.STOPPED);
log(lifecycleMonitor, Stage.RESTARTED);
log(lifecycleMonitor, Stage.DESTROYED);
}
private void log(ActivityLifecycleMonitor lifecycleMonitor,
Stage stage) {
for (Activity a : lifecycleMonitor
.getActivitiesInStage(stage)) {
Log.e("TEST", a.getClass().getSimpleName() +
" is in state " + stage);
}
return lifecycleMonitor.getLifecycleStageOf(activity) == stage;
}
@Override
public String getDescription() {
return "Wait for activity " + clazz.getName() + " in stage " +
stage.name() + " within " + timeout +
" milliseconds.";
return "Wait for activity " + activity.getClass().getName() +
" to resume within " + TIMEOUT_MS + " milliseconds.";
}
};
}
private static abstract class CustomViewAction implements ViewAction {
private final long timeout;
public CustomViewAction() {
this(TIMEOUT_MS);
}
public CustomViewAction(long timeout) {
this.timeout = timeout;
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
@@ -130,7 +74,7 @@ public class ViewActions {
@Override
public void perform(UiController uiController, View view) {
uiController.loopMainThreadUntilIdle();
long endTime = currentTimeMillis() + timeout;
long endTime = currentTimeMillis() + TIMEOUT_MS;
do {
if (exitConditionTrue(view)) return;
uiController.loopMainThreadForAtLeast(WAIT_MS);

View File

@@ -3,10 +3,7 @@ package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.account.SignInTestCreateAccount;
import org.briarproject.briar.android.account.SignInTestSignIn;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
@@ -19,7 +16,6 @@ import dagger.Component;
@Component(modules = {
AppModule.class,
AttachmentModule.class,
ClockModule.class,
MediaModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
@@ -30,8 +26,4 @@ public interface BriarUiTestComponent extends AndroidComponent {
void inject(NavDrawerActivityTest test);
void inject(SignInTestCreateAccount test);
void inject(SignInTestSignIn test);
}

View File

@@ -1,65 +0,0 @@
package org.briarproject.briar.android.account;
import android.view.Gravity;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarUiTestComponent;
import org.briarproject.briar.android.UiTest;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.espresso.contrib.DrawerActions;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.contrib.DrawerMatchers.isClosed;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.briarproject.briar.android.ViewActions.waitFor;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.hamcrest.Matchers.endsWith;
@RunWith(AndroidJUnit4.class)
public class SignInTestCreateAccount extends UiTest {
@Override
protected void inject(BriarUiTestComponent component) {
component.inject(this);
}
@Test
public void createAccount() throws Exception {
accountManager.deleteAccount();
accountManager.createAccount(USERNAME, PASSWORD);
startActivity(SplashScreenActivity.class);
lifecycleManager.waitForStartup();
waitFor(NavDrawerActivity.class);
// open nav drawer
onView(withId(R.id.drawer_layout))
.check(matches(isClosed(Gravity.START)))
.perform(DrawerActions.open());
// click onboarding away (once shown)
onView(isRoot()).perform(waitUntilMatches(hasDescendant(
withClassName(endsWith("PromptView")))));
onView(withClassName(endsWith("PromptView")))
.perform(click());
// sign-out manually
onView(withText(R.string.sign_out_button))
.check(matches(isDisplayed()))
.perform(click());
lifecycleManager.waitForShutdown();
}
}

View File

@@ -1,59 +0,0 @@
package org.briarproject.briar.android.account;
import android.view.Gravity;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarUiTestComponent;
import org.briarproject.briar.android.UiTest;
import org.briarproject.briar.android.login.StartupActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.contrib.DrawerMatchers.isClosed;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.briarproject.briar.android.ViewActions.waitFor;
import static org.hamcrest.CoreMatchers.allOf;
/**
* This relies on class sorting to run after {@link SignInTestCreateAccount}.
*/
@RunWith(AndroidJUnit4.class)
public class SignInTestSignIn extends UiTest {
@Override
protected void inject(BriarUiTestComponent component) {
component.inject(this);
}
@Test
public void signIn() throws Exception {
startActivity(SplashScreenActivity.class);
waitFor(StartupActivity.class);
// enter password
onView(withId(R.id.edit_password))
.check(matches(isDisplayed()))
.perform(replaceText(PASSWORD));
onView(withId(R.id.btn_sign_in))
.check(matches(allOf(isDisplayed(), isEnabled())))
.perform(click());
lifecycleManager.waitForStartup();
waitFor(NavDrawerActivity.class);
// ensure nav drawer is visible
onView(withId(R.id.drawer_layout))
.check(matches(isClosed(Gravity.START)));
}
}

View File

@@ -10,15 +10,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import androidx.test.filters.LargeTest;
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.Assert.fail;
import static org.junit.Assume.assumeTrue;
@LargeTest
@RunWith(Parameterized.class)
public class PngSuiteImageCompressorTest
extends AbstractImageCompressorTest {

View File

@@ -12,14 +12,11 @@ import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import androidx.test.filters.LargeTest;
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;
@LargeTest
@RunWith(Parameterized.class)
public class PngSuiteImageSizeCalculatorTest
extends AbstractImageSizeCalculatorTest {

View File

@@ -0,0 +1,39 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.content.Intent;
import org.briarproject.briar.R;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@RunWith(AndroidJUnit4.class)
public class ConversationActivityNotSignedInTest {
@Rule
public ActivityTestRule<ConversationActivity> testRule =
new ActivityTestRule<>(ConversationActivity.class, false, false);
@Test
public void openWithoutSignedIn() {
Context targetContext = getInstrumentation().getTargetContext();
Intent intent = new Intent(targetContext, ConversationActivity.class);
intent.putExtra(CONTACT_ID, 1);
testRule.launchActivity(intent);
onView(withText(R.string.sign_in_button))
.perform(waitUntilMatches(isDisplayed()));
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
@@ -18,7 +17,6 @@ import dagger.Component;
@Component(modules = {
AppModule.class,
AttachmentModule.class,
ClockModule.class,
MediaModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
@@ -33,6 +31,4 @@ public interface BriarUiTestComponent extends AndroidComponent {
void inject(SettingsActivityScreenshotTest test);
void inject(PromoVideoTest test);
}

View File

@@ -1,41 +0,0 @@
package org.briarproject.briar.android;
import android.view.View;
import org.hamcrest.Matcher;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import static androidx.test.espresso.action.GeneralLocation.VISIBLE_CENTER;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
public class OverlayTapViewAction implements ViewAction {
public static ViewAction visualClick(OverlayView overlayView) {
return new OverlayTapViewAction(overlayView);
}
private final OverlayView overlayView;
public OverlayTapViewAction(OverlayView overlayView) {
this.overlayView = overlayView;
}
@Override
public Matcher<View> getConstraints() {
return isDisplayingAtLeast(90);
}
@Override
public String getDescription() {
return null;
}
@Override
public void perform(UiController uiController, View view) {
float[] coordinates = VISIBLE_CENTER.calculateCoordinates(view);
overlayView.tap(coordinates);
}
}

View File

@@ -1,86 +0,0 @@
package org.briarproject.briar.android;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.view.View;
import android.view.WindowManager;
import java.util.Random;
import javax.annotation.Nullable;
import static android.content.Context.WINDOW_SERVICE;
import static android.provider.Settings.canDrawOverlays;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
import static org.junit.Assert.assertTrue;
/**
* A full-screen overlay used to make taps visible in instrumentation tests.
*/
public class OverlayView extends View {
public static OverlayView attach(Context ctx) throws Throwable {
assertTrue(canDrawOverlays(ctx));
OverlayView view = new OverlayView(getApplicationContext());
runOnUiThread(() -> attachInternal(ctx, view));
return view;
}
private static void attachInternal(Context ctx, OverlayView view) {
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
WindowManager wm = (WindowManager) ctx.getSystemService(WINDOW_SERVICE);
wm.addView(view, params);
}
private final Random random = new Random();
private final Paint paint;
private final int yOffset;
@Nullable
private float[] coordinates;
public OverlayView(Context ctx) {
super(ctx);
int resourceId = getResources()
.getIdentifier("status_bar_height", "dimen", "android");
yOffset = getResources().getDimensionPixelSize(resourceId);
paint = new Paint();
paint.setAntiAlias(true);
paint.setARGB(175, 255, 0, 0);
setWillNotDraw(false);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
void tap(float[] coordinates) {
this.coordinates = coordinates;
invalidate();
new Handler().postDelayed(this::untap, 750);
}
private void untap() {
this.coordinates = null;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (coordinates == null) return;
float x = coordinates[0] + random.nextInt(42);
float y = coordinates[1] - yOffset + random.nextInt(13);
canvas.drawCircle(x, y, 42, paint);
}
}

View File

@@ -1,285 +0,0 @@
package org.briarproject.briar.android;
import android.view.View;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.briar.R;
import org.briarproject.briar.android.account.SetupActivity;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import javax.inject.Inject;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject;
import androidx.test.uiautomator.UiSelector;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static java.lang.Thread.sleep;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.briar.android.OverlayTapViewAction.visualClick;
import static org.briarproject.briar.android.ViewActions.waitFor;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
import static org.hamcrest.CoreMatchers.allOf;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
public class PromoVideoTest extends ScreenshotTest {
// we can leave isFilming to false (to speed up CI)
// and only set it to true when doing recordings
private static final boolean isFilming = false;
private static final int DELAY_SMALL = isFilming ? 4_000 : 0;
private static final int DELAY_MEDIUM = isFilming ? 7_500 : 0;
private static final int DELAY_LONG = isFilming ? 10_000 : 0;
@Rule
public ActivityScenarioRule<SplashScreenActivity> testRule =
new ActivityScenarioRule<>(SplashScreenActivity.class);
@Inject
protected ContactManager contactManager;
private OverlayView overlayView;
@Override
protected void inject(BriarUiTestComponent component) {
component.inject(this);
accountManager.deleteAccount();
}
@Test
public void createAccountAddContact() throws Throwable {
if (isFilming) {
// Using this breaks emulator CI tests for some reason.
// Only use it for filming for now until we have time to debug this.
overlayView = OverlayView.attach(getApplicationContext());
}
// Splash screen shows logo
onView(withId(R.id.logoView))
.perform(waitUntilMatches(isDisplayed()));
// It takes a long time for SetupActivity to start after the splash,
// (because it is shown longer for videos), so increase timeout.
if (!isFilming) waitFor(SetupActivity.class, 30_000);
// Note: We use waiting code only when not filming,
// to make the test reliable for CI. Otherwise, we used fixed
// delays to deterministically align with subtitles.
sleep(DELAY_LONG);
// Enter username
onView(withText(R.string.setup_title))
.perform(waitUntilMatches(isDisplayed()));
sleep(DELAY_SMALL);
onView(withId(R.id.nickname_entry))
.check(matches(isDisplayed()))
.perform(replaceText(USERNAME));
closeKeyboard(withId(R.id.nickname_entry));
sleep(DELAY_SMALL);
doClick(withId(R.id.next));
sleep(DELAY_MEDIUM);
// Enter password
doClick(withId(R.id.password_entry), 1000);
onView(withId(R.id.password_entry))
.check(matches(isDisplayed()))
.perform(replaceText(PASSWORD));
sleep(DELAY_SMALL);
doClick(withId(R.id.password_confirm), 1000);
onView(withId(R.id.password_confirm))
.check(matches(isDisplayed()))
.perform(replaceText(PASSWORD));
sleep(DELAY_SMALL);
// click next or create account
doClick(withId(R.id.next));
sleep(DELAY_SMALL);
// White-list Doze if needed
if (needsDozeWhitelisting(getApplicationContext())) {
doClick(withText(R.string.setup_doze_button));
UiDevice device = UiDevice.getInstance(getInstrumentation());
UiObject allowButton = device.findObject(
new UiSelector().className("android.widget.Button")
.index(1));
allowButton.click();
doClick(withId(R.id.next));
}
lifecycleManager.waitForStartup();
assertTrue(accountManager.hasDatabaseKey());
sleep(DELAY_SMALL);
// wait for contact list to be shown
if (!isFilming) waitFor(NavDrawerActivity.class);
// clicking the FAB doesn't work, so we click its inner FAB as well
onView(withId(R.id.speedDial))
.check(matches(isDisplayed()))
.perform(click());
doClick(withId(R.id.fab_main)); // this is inside R.id.speedDial
sleep(DELAY_MEDIUM);
// click adding contact at a distance menu item
doClick(withText(R.string.add_contact_remotely_title));
sleep(DELAY_LONG);
// enter briar:// link
String link =
"briar://ab54fpik6sjyetzjhlwto2fv7tspibx2uhpdnei4tdidkvjpbphvy";
doClick(withId(R.id.pasteButton));
onView(withId(R.id.linkInput))
.perform(waitUntilMatches(isDisplayed()))
.perform(replaceText(link));
sleep(DELAY_MEDIUM);
doClick(withId(R.id.addButton));
sleep(DELAY_MEDIUM);
// enter contact alias
String contactName = getApplicationContext()
.getString(R.string.screenshot_bob);
doClick(withId(R.id.contactNameInput), 1000);
onView(withId(R.id.contactNameInput))
.perform(waitUntilMatches(isDisplayed()))
.perform(replaceText(contactName));
sleep(DELAY_SMALL);
closeKeyboard(withId(R.id.contactNameInput));
sleep(DELAY_SMALL);
// add pending contact
onView(withId(R.id.addButton)).perform(scrollTo());
doClick(withId(R.id.addButton));
sleep(DELAY_LONG);
// wait for pending contact list activity to be shown
if (!isFilming) {
waitFor(PendingContactListActivity.class);
waitFor(allOf(withText(R.string.pending_contact_requests),
isDisplayed()));
}
// remove pending contact
for (Pair<PendingContact, PendingContactState> p : contactManager
.getPendingContacts()) {
contactManager.removePendingContact(p.getFirst().getId());
}
// add contact and make them appear online
Contact bob = testDataCreator.addContact(contactName, false, true);
sleep(DELAY_SMALL);
connectionRegistry.registerIncomingConnection(bob.getId(), ID, () -> {
});
sleep(DELAY_LONG);
// wait for contact list to be shown
if (!isFilming) {
waitFor(NavDrawerActivity.class);
waitFor(allOf(withText(R.string.contact_list_button),
isDisplayed()));
waitFor(allOf(withId(R.id.recyclerView), isDisplayed()));
}
// click on new contact
doItemClick(withId(R.id.recyclerView), 0);
sleep(DELAY_MEDIUM);
// bring up keyboard
doClick(withId(R.id.input_text), DELAY_SMALL);
String msg1 = getApplicationContext()
.getString(R.string.screenshot_message_1);
onView(withId(R.id.input_text))
.perform(waitUntilMatches(isEnabled()))
.perform(replaceText(msg1));
sleep(DELAY_SMALL);
doClick(withId(R.id.compositeSendButton));
sleep(DELAY_SMALL);
// send emoji
doClick(withId(R.id.emoji_toggle), DELAY_SMALL);
onView(withId(R.id.input_text))
.perform(replaceText("\uD83D\uDE0E"));
sleep(DELAY_SMALL);
doClick(withId(R.id.compositeSendButton));
// close keyboard
closeKeyboard(withId(R.id.compositeSendButton));
sleep(DELAY_LONG);
}
private void doClick(final Matcher<View> viewMatcher, long sleepMs)
throws InterruptedException {
doClick(viewMatcher);
if (isFilming) sleep(sleepMs);
}
private void doClick(final Matcher<View> viewMatcher)
throws InterruptedException {
if (isFilming) {
onView(viewMatcher)
.perform(waitUntilMatches(isDisplayed()))
.perform(visualClick(overlayView));
sleep(500);
}
onView(viewMatcher)
.perform(waitUntilMatches(allOf(isDisplayed(), isEnabled())))
.perform(click());
}
private void doItemClick(final Matcher<View> viewMatcher, int pos)
throws InterruptedException {
if (isFilming) {
onView(viewMatcher).perform(
actionOnItemAtPosition(pos, visualClick(overlayView)));
sleep(500);
}
onView(viewMatcher).perform(
actionOnItemAtPosition(pos, click()));
}
private void closeKeyboard(final Matcher<View> viewMatcher)
throws InterruptedException {
if (isFilming) sleep(750);
onView(viewMatcher).perform(closeSoftKeyboard());
}
}

View File

@@ -3,8 +3,6 @@ package org.briarproject.briar.android;
import android.app.Activity;
import android.util.Log;
import com.jraska.falcon.Falcon.UnableToTakeScreenshotException;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.test.TestDataCreator;
@@ -12,7 +10,6 @@ import org.junit.ClassRule;
import javax.inject.Inject;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import tools.fastlane.screengrab.FalconScreenshotStrategy;
import tools.fastlane.screengrab.Screengrab;
import tools.fastlane.screengrab.locale.LocaleTestRule;
@@ -29,15 +26,9 @@ public abstract class ScreenshotTest extends UiTest {
@Inject
protected Clock clock;
protected void screenshot(String name, ActivityScenarioRule<?> rule) {
rule.getScenario().onActivity(activity -> screenshot(name, activity));
}
protected void screenshot(String name, Activity activity) {
try {
Screengrab.screenshot(name, new FalconScreenshotStrategy(activity));
} catch (UnableToTakeScreenshotException e) {
Log.e("Screengrab", "Error taking screenshot", e);
} catch (RuntimeException e) {
if (e.getMessage() == null ||
!e.getMessage().equals("Unable to capture screenshot."))

View File

@@ -10,7 +10,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject;
@@ -22,27 +22,30 @@ import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
public class SetupDataTest extends ScreenshotTest {
@Rule
public ActivityScenarioRule<SetupActivity> testRule =
new ActivityScenarioRule<>(SetupActivity.class);
public IntentsTestRule<SetupActivity> testRule =
new IntentsTestRule<SetupActivity>(SetupActivity.class) {
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
accountManager.deleteAccount();
}
};
@Override
protected void inject(BriarUiTestComponent component) {
component.inject(this);
accountManager.deleteAccount();
}
@Test
@@ -56,7 +59,7 @@ public class SetupDataTest extends ScreenshotTest {
onView(withId(R.id.nickname_entry))
.perform(waitUntilMatches(withText(USERNAME)));
screenshot("manual_create_account", testRule);
screenshot("manual_create_account", testRule.getActivity());
onView(withId(R.id.next))
.check(matches(isDisplayed()))
@@ -70,7 +73,7 @@ public class SetupDataTest extends ScreenshotTest {
.check(matches(isDisplayed()))
.perform(typeText(PASSWORD));
onView(withId(R.id.next))
.check(matches(allOf(isDisplayed(), isEnabled())))
.check(matches(isDisplayed()))
.perform(click());
// White-list Doze if needed
@@ -91,6 +94,14 @@ public class SetupDataTest extends ScreenshotTest {
lifecycleManager.waitForStartup();
assertTrue(accountManager.hasDatabaseKey());
createTestData();
// close expiry warning
onView(withId(R.id.expiryWarning))
.perform(waitUntilMatches(isDisplayed()));
onView(withId(R.id.expiryWarningClose))
.check(matches(isDisplayed()));
onView(withId(R.id.expiryWarningClose))
.perform(click());
}
private void createTestData() {
@@ -105,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, false, true);
Contact bob = testDataCreator.addContact(bobName, true);
// TODO add messages

View File

@@ -11,29 +11,32 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.contrib.DrawerActions;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.contrib.DrawerMatchers.isClosed;
import static androidx.test.espresso.contrib.RecyclerViewActions.scrollTo;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withChild;
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;
import static org.junit.Assume.assumeTrue;
import static org.hamcrest.CoreMatchers.is;
@RunWith(AndroidJUnit4.class)
public class SettingsActivityScreenshotTest extends ScreenshotTest {
@Rule
public CleanAccountTestRule<SettingsActivity> testRule =
new CleanAccountTestRule<>(SettingsActivity.class);
public ActivityTestRule<SettingsActivity> testRule =
new ActivityTestRule<>(SettingsActivity.class);
@Override
protected void inject(BriarUiTestComponent component) {
@@ -45,10 +48,6 @@ public class SettingsActivityScreenshotTest extends ScreenshotTest {
onView(withText(R.string.settings_button))
.check(matches(isDisplayed()));
onView(withText(R.string.display_settings_title))
.perform(waitUntilMatches(isDisplayed()))
.perform(click(), click());
screenshot("manual_dark_theme_settings", testRule.getActivity());
// switch to dark theme
@@ -67,9 +66,6 @@ public class SettingsActivityScreenshotTest extends ScreenshotTest {
onView(withText(R.string.settings_button))
.check(matches(isDisplayed()))
.perform(click());
onView(withText(R.string.display_settings_title))
.check(matches(isDisplayed()))
.perform(click());
onView(withText(R.string.pref_theme_title))
.check(matches(isDisplayed()))
.perform(click());
@@ -80,17 +76,20 @@ public class SettingsActivityScreenshotTest extends ScreenshotTest {
@Test
public void appLock() {
assumeTrue("device has no screen lock",
hasScreenLock(getApplicationContext()));
// scroll down
onView(withClassName(is(RecyclerView.class.getName())))
.perform(scrollTo(hasDescendant(
// scroll down a bit more to have settings in the middle
withText(R.string.panic_setting))));
onView(withText(R.string.security_settings_title))
.perform(waitUntilMatches(isDisplayed()))
.perform(click(), click());
// wait for settings to get loaded and enabled
onView(withText(R.string.tor_mobile_data_title))
.perform(waitUntilMatches(isEnabled()));
// ensure app lock is displayed and enabled
onView(withText(R.string.pref_lock_title))
.check(matches(isDisplayed()))
.perform(waitUntilMatches(isEnabled()))
.check(matches(isEnabled()))
.perform(click());
onView(withChild(withText(R.string.pref_lock_timeout_title)))
.check(matches(isDisplayed()))
@@ -105,10 +104,11 @@ public class SettingsActivityScreenshotTest extends ScreenshotTest {
@Test
public void torSettings() {
// click network/connections settings
onView(withText(R.string.network_settings_title))
.perform(waitUntilMatches(isDisplayed()))
.perform(click(), click());
// scroll down
onView(withClassName(is(RecyclerView.class.getName())))
.perform(scrollTo(hasDescendant(
// scroll down a bit more to have settings in the middle
withText(R.string.pref_lock_timeout_title))));
// wait for settings to get loaded and enabled
onView(withText(R.string.tor_network_setting))

View File

@@ -27,10 +27,7 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
@@ -41,7 +38,6 @@
android:name="org.briarproject.briar.android.BriarApplicationImpl"
android:allowBackup="false"
android:banner="@mipmap/tv_banner"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher_round"
@@ -342,7 +338,7 @@
</activity>
<activity
android:name="org.briarproject.briar.android.contact.add.nearby.AddNearbyContactActivity"
android:name="org.briarproject.briar.android.keyagreement.ContactExchangeActivity"
android:label="@string/add_contact_title"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar">

View File

@@ -29,7 +29,6 @@ import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
@@ -37,17 +36,12 @@ import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.settings.ConnectionsFragment;
import org.briarproject.briar.android.settings.NotificationsFragment;
import org.briarproject.briar.android.settings.SecurityFragment;
import org.briarproject.briar.android.settings.SettingsFragment;
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.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -82,7 +76,6 @@ import dagger.Component;
BriarAccountModule.class,
AppModule.class,
AttachmentModule.class,
ClockModule.class,
MediaModule.class
})
public interface AndroidComponent
@@ -191,8 +184,6 @@ public interface AndroidComponent
Thread.UncaughtExceptionHandler exceptionHandler();
AutoDeleteManager autoDeleteManager();
void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService);
@@ -202,12 +193,4 @@ public interface AndroidComponent
void inject(EmojiTextInputView textInputView);
void inject(BriarModelLoader briarModelLoader);
void inject(SettingsFragment settingsFragment);
void inject(ConnectionsFragment connectionsFragment);
void inject(SecurityFragment securityFragment);
void inject(NotificationsFragment notificationsFragment);
}

View File

@@ -37,7 +37,6 @@ import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.briarproject.briar.android.util.BriarNotificationBuilder;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
import org.briarproject.briar.api.conversation.ConversationResponse;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent;
import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent;
@@ -227,12 +226,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
} else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
if (p.getMessageHeader() instanceof ConversationResponse) {
ConversationResponse r =
(ConversationResponse) p.getMessageHeader();
// don't show notification for own auto-decline responses
if (r.isAutoDecline()) return;
}
showContactNotification(p.getContactId());
} else if (e instanceof GroupMessageAddedEvent) {
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.StrictMode;
@@ -31,11 +30,9 @@ 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.blog.BlogModule;
import org.briarproject.briar.android.contact.ContactListModule;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactModule;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.introduction.IntroductionModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.logging.LoggingModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
@@ -77,7 +74,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = {
SetupModule.class,
DozeHelperModule.class,
AddNearbyContactModule.class,
ContactExchangeModule.class,
LoggingModule.class,
LoginModule.class,
NavDrawerModule.class,
@@ -85,9 +82,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
SettingsModule.class,
DevReportModule.class,
ContactListModule.class,
IntroductionModule.class,
// below need to be within same scope as ViewModelProvider.Factory
BlogModule.class,
ForumModule.class,
GroupListModule.class,
GroupConversationModule.class,
@@ -116,11 +111,6 @@ public class AppModule {
this.application = application;
}
public static AndroidComponent getAndroidComponent(Context ctx) {
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
return app.getApplicationComponent();
}
@Provides
@Singleton
Application providesApplication() {
@@ -286,21 +276,11 @@ public class AppModule {
@Override
public boolean shouldEnableImageAttachments() {
return true;
return IS_DEBUG_BUILD;
}
@Override
public boolean shouldEnableProfilePictures() {
return true;
}
@Override
public boolean shouldEnableDisappearingMessages() {
return true;
}
@Override
public boolean shouldEnableConnectViaBluetooth() {
return IS_DEBUG_BUILD;
}
};

View File

@@ -19,6 +19,4 @@ public interface BriarApplication extends BrambleApplication {
SharedPreferences getDefaultSharedPreferences();
boolean isRunningInBackground();
boolean isInstrumentationTest();
}

View File

@@ -151,9 +151,4 @@ public class BriarApplicationImpl extends Application
ActivityManager.getMyMemoryState(info);
return (info.importance != IMPORTANCE_FOREGROUND);
}
@Override
public boolean isInstrumentationTest() {
return false;
}
}

View File

@@ -301,9 +301,7 @@ public class BriarService extends Service {
LOG.info("Interrupted while waiting for shutdown");
}
LOG.info("Exiting");
if (!app.isInstrumentationTest()) {
System.exit(0);
}
System.exit(0);
}, "BackgroundShutdown");
}, "BackgroundShutdown");
}

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