Compare commits

..

29 Commits

Author SHA1 Message Date
Daniel Lublin
d3692b2a97 WIP add removabledrive test ui 2021-06-09 13:44:20 +02:00
Torsten Grote
bcbc96dc2d Merge branch '2037-create-removabledriveviewmodel' into '1802-sync-via-removable-storage'
Add RemovableDriveViewModel

See merge request briar/briar!1475
2021-06-09 11:32:10 +00:00
Daniel Lublin
1ddcd6cfff Make pkg private 2021-06-08 20:31:23 +02:00
Daniel Lublin
fd810f5c16 Move to new removabledrive package 2021-06-08 12:25:09 +02:00
Daniel Lublin
3f5e131250 Use US locale for now 2021-06-08 12:18:33 +02:00
Daniel Lublin
3ee516599d Add initial RemovableDriveViewModel 2021-06-07 13:17:50 +02:00
akwizgran
a5fb3bb4a4 Merge branch '2016-2017-2018-removable-drive-reader-writer' into '1802-sync-via-removable-storage'
Create removable drive manager and reader/writer tasks

See merge request briar/briar!1458
2021-05-11 14:01:53 +00:00
akwizgran
eae329cdfa Refactor manager and tasks to remove reliance on files. 2021-05-11 12:19:16 +01:00
akwizgran
0ce0551f0d Update progress of writer task. 2021-05-11 12:19:16 +01:00
akwizgran
a198e7d08e Ensure that observers see the final state even if they're added late. 2021-05-11 12:19:16 +01:00
akwizgran
bca6f1506e Add integration test for syncing via removable drives. 2021-05-11 12:19:16 +01:00
akwizgran
e420201b00 Implement RemovableDriveWriterTask, except for progress updates. 2021-05-11 12:19:16 +01:00
akwizgran
03248d04e5 Fix typo in class names. 2021-05-11 12:19:16 +01:00
akwizgran
2c39b02644 Implement RemovableDriverReaderTask. 2021-05-11 12:19:16 +01:00
akwizgran
c9c6f3682c Add task factory. 2021-05-11 12:19:16 +01:00
akwizgran
8f4a0ef030 Add removable drive manager with placeholder task implementations. 2021-05-11 12:19:14 +01:00
akwizgran
5fe22bcd57 Merge branch '2035-android-removable-drive-plugin' into '1802-sync-via-removable-storage'
Add Android implementation of RemovableDrivePlugin

See merge request briar/briar!1457
2021-05-11 11:13:14 +00:00
akwizgran
b4880af7e2 Add Android implementation of RemovableDrivePlugin. 2021-05-10 14:19:24 +01:00
akwizgran
51d21bd669 Decouple RemovableDrivePlugin from FileConstants. 2021-05-10 13:48:12 +01:00
Torsten Grote
b8f3728a0d Merge branch '2015-removable-drive-plugin' into '1802-sync-via-removable-storage'
Create RemovableDrivePlugin

See merge request briar/briar!1454
2021-05-10 12:47:21 +00:00
akwizgran
bbfd4f137d Merge branch '2013-db-method-for-amount-of-data-to-sync' into '1802-sync-via-removable-storage'
Add DB method for getting amount of data to sync

See merge request briar/briar!1452
2021-05-10 12:00:11 +00:00
Daniel Lublin
7e3ca76dd1 Merge branch '2014-messages-sent-event' into '1802-sync-via-removable-storage'
Update MessagesSentEvent to include amount of data sent

See merge request briar/briar!1453
2021-05-10 11:44:27 +00:00
akwizgran
524c8d26f8 Don't inject default RemovableDrivePluginFactory on Android. 2021-05-07 17:48:39 +01:00
akwizgran
7eccf7dac1 Decouple removable drive plugin from java.io.File for portability. 2021-05-07 17:36:10 +01:00
akwizgran
0bc06248ed Clean up plugin injection code, remove unused module. 2021-05-06 16:59:45 +01:00
akwizgran
c999f05cc7 Configure removable drive plugin for Android. 2021-05-06 16:59:45 +01:00
akwizgran
428269b312 Add removable drive plugin. 2021-05-06 16:59:45 +01:00
akwizgran
588e05ce83 Update MessagesSentEvent to include amount of data sent. 2021-05-06 16:20:15 +01:00
akwizgran
f7875c99b6 Add DB method for getting amount of data to sync. 2021-05-05 17:52:37 +01:00
98 changed files with 1849 additions and 532 deletions

View File

@@ -15,8 +15,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 10304
versionName "1.3.4"
versionCode 10303
versionName "1.3.3"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final BackoffFactory backoffFactory;
@Inject
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
AndroidExecutor androidExecutor,
AndroidWakeLockManager wakeLockManager,

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.plugin.file;
import android.app.Application;
import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
private final Application app;
AndroidRemovableDrivePlugin(Application app, int maxLatency) {
super(maxLatency);
this.app = app;
}
@Override
InputStream openInputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
return app.getContentResolver().openInputStream(Uri.parse(uri));
}
@Override
OutputStream openOutputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
return app.getContentResolver().openOutputStream(Uri.parse(uri));
}
}

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.plugin.file;
import android.app.Application;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@Immutable
@NotNullByDefault
public class AndroidRemovableDrivePluginFactory implements
SimplexPluginFactory {
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
private final Application app;
@Inject
AndroidRemovableDrivePluginFactory(Application app) {
this.app = app;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new AndroidRemovableDrivePlugin(app, MAX_LATENCY);
}
}

View File

@@ -37,7 +37,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private final Application app;
@Inject
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
EventBus eventBus,
BackoffFactory backoffFactory,

View File

@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
private final File torDirectory;
@Inject
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
Application app,
NetworkManager networkManager,

View File

@@ -10,20 +10,17 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Scanner;
import javax.annotation.Nullable;
import static android.content.Context.MODE_PRIVATE;
import static android.os.Build.VERSION.SDK_INT;
import static java.lang.Runtime.getRuntime;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
@@ -119,24 +116,6 @@ public class AndroidUtils {
* Returns an array of supported content types for image attachments.
*/
public static String[] getSupportedImageContentTypes() {
return new String[] {
"image/jpeg",
"image/png",
"image/gif",
"image/webp"
};
}
@Nullable
public static String getSystemProperty(String propName) {
try {
Process p = getRuntime().exec("getprop " + propName);
Scanner s = new Scanner(p.getInputStream());
String line = s.nextLine();
s.close();
return line;
} catch (SecurityException | IOException e) {
return null;
}
return new String[] {"image/jpeg", "image/png", "image/gif"};
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface Consumer<T> {
void accept(T t);
}

View File

@@ -292,6 +292,16 @@ public interface DatabaseComponent extends TransactionManager {
*/
Message getMessage(Transaction txn, MessageId m) throws DbException;
/**
* Returns the total length, including headers, of any messages that are
* eligible to be sent to the given contact via a transport with the given
* max latency.
* <p/>
* Read-only.
*/
long getMessageBytesToSend(Transaction txn, ContactId c, int maxLatency)
throws DbException;
/**
* Returns the IDs of all delivered messages in the given group.
* <p/>

View File

@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.keyagreement.event;
import org.briarproject.bramble.api.event.Event;
/**
* An event that is broadcast when a BQP protocol begins.
* An event that is broadcast when a BQP protocol completes.
*/
public class KeyAgreementStartedEvent extends Event {
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.api.plugin;
package org.briarproject.bramble.api.plugin.file;
public interface FileConstants {

View File

@@ -0,0 +1,11 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.plugin.TransportId;
public interface RemovableDriveConstants {
TransportId ID = new TransportId("org.briarproject.bramble.drive");
String PROP_PATH = "path";
String PROP_URI = "uri";
}

View File

@@ -0,0 +1,41 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import javax.annotation.Nullable;
@NotNullByDefault
public interface RemovableDriveManager {
/**
* Returns the currently running reader task for the given contact,
* or null if no task is running.
*/
@Nullable
RemovableDriveTask getCurrentReaderTask(ContactId c);
/**
* Returns the currently running writer task for the given contact,
* or null if no task is running.
*/
@Nullable
RemovableDriveTask getCurrentWriterTask(ContactId c);
/**
* Starts and returns a reader task for the given contact, reading from
* a stream described by the given transport properties. If a reader task
* for the contact is already running, it will be returned and the
* transport properties will be ignored.
*/
RemovableDriveTask startReaderTask(ContactId c, TransportProperties p);
/**
* Starts and returns a writer task for the given contact, writing to
* a stream described by the given transport properties. If a writer task
* for the contact is already running, it will be returned and the
* transport properties will be ignored.
*/
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
@NotNullByDefault
public interface RemovableDriveTask extends Runnable {
/**
* Returns the {@link TransportProperties} that were used for creating
* this task.
*/
TransportProperties getTransportProperties();
/**
* Adds an observer to the task. The observer will be notified of state
* changes on the event thread. If the task has already finished, the
* observer will be notified of its final state.
*/
void addObserver(Consumer<State> observer);
/**
* Removes an observer from the task.
*/
void removeObserver(Consumer<State> observer);
class State {
private final long done, total;
private final boolean finished, success;
public State(long done, long total, boolean finished, boolean success) {
this.done = done;
this.total = total;
this.finished = finished;
this.success = success;
}
/**
* Returns the total length in bytes of the messages read or written
* so far.
*/
public long getDone() {
return done;
}
/**
* Returns the total length in bytes of the messages that will have
* been read or written when the task is complete, or zero if the
* total is unknown.
*/
public long getTotal() {
return total;
}
public boolean isFinished() {
return finished;
}
public boolean isSuccess() {
return success;
}
}
}

View File

@@ -18,11 +18,13 @@ public class MessagesSentEvent extends Event {
private final ContactId contactId;
private final Collection<MessageId> messageIds;
private final long totalLength;
public MessagesSentEvent(ContactId contactId,
Collection<MessageId> messageIds) {
Collection<MessageId> messageIds, long totalLength) {
this.contactId = contactId;
this.messageIds = messageIds;
this.totalLength = totalLength;
}
public ContactId getContactId() {
@@ -32,4 +34,8 @@ public class MessagesSentEvent extends Event {
public Collection<MessageId> getMessageIds() {
return messageIds;
}
public long getTotalLength() {
return totalLength;
}
}

View File

@@ -347,6 +347,16 @@ interface Database<T> {
*/
Message getMessage(T txn, MessageId m) throws DbException;
/**
* Returns the total length, including headers, of any messages that are
* eligible to be sent to the given contact via a transport with the given
* max latency.
* <p/>
* Read-only.
*/
long getMessageBytesToSend(T txn, ContactId c, int maxLatency)
throws DbException;
/**
* Returns the IDs and states of all dependencies of the given message.
* For missing dependencies and dependencies in other groups, the state

View File

@@ -415,14 +415,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException();
Collection<MessageId> ids =
db.getMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) {
messages.add(db.getMessage(txn, m));
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
}
if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids));
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages;
}
@@ -467,14 +470,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException();
Collection<MessageId> ids =
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) {
messages.add(db.getMessage(txn, m));
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
}
if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids));
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages;
}
@@ -569,6 +575,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessage(txn, m);
}
@Override
public long getMessageBytesToSend(Transaction transaction, ContactId c,
int maxLatency) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getMessageBytesToSend(txn, c, maxLatency);
}
@Override
public Collection<MessageId> getMessageIds(Transaction transaction,
GroupId g) throws DbException {

View File

@@ -1758,6 +1758,37 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public long getMessageBytesToSend(Connection txn, ContactId c,
int maxLatency) throws DbException {
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT SUM(length) FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR eta > ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
rs = ps.executeQuery();
rs.next();
long total = rs.getInt(1);
rs.close();
ps.close();
return total;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public Collection<MessageId> getMessageIds(Connection txn, GroupId g)
throws DbException {

View File

@@ -473,16 +473,6 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
return discoverSemaphore.availablePermits() == 0;
}
@Override
public void disablePolling() {
connectionLimiter.startLimiting();
}
@Override
public void enablePolling() {
connectionLimiter.endLimiting();
}
@Override
public DuplexTransportConnection discoverAndConnectForSetup(String uuid) {
DuplexTransportConnection conn = discoverAndConnect(uuid);
@@ -511,9 +501,9 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
if (s.getNamespace().equals(ID.getString()))
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
} else if (e instanceof KeyAgreementListeningEvent) {
connectionLimiter.startLimiting();
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
connectionLimiter.endLimiting();
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
} else if (e instanceof RemoteTransportPropertiesUpdatedEvent) {
RemoteTransportPropertiesUpdatedEvent r =
(RemoteTransportPropertiesUpdatedEvent) e;

View File

@@ -7,15 +7,14 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
interface BluetoothConnectionLimiter {
/**
* Tells the limiter to not allow regular polling connections (because we
* are about to do key agreement, or connect via BT for setup).
* Informs the limiter that key agreement has started.
*/
void startLimiting();
void keyAgreementStarted();
/**
* Tells the limiter to no longer limit regular polling connections.
* Informs the limiter that key agreement has ended.
*/
void endLimiting();
void keyAgreementEnded();
/**
* Returns true if a contact connection can be opened. This method does not

View File

@@ -30,37 +30,34 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
private final List<DuplexTransportConnection> connections =
new LinkedList<>();
@GuardedBy("lock")
private int limitingInProgress = 0;
private boolean keyAgreementInProgress = false;
BluetoothConnectionLimiterImpl(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public void startLimiting() {
public void keyAgreementStarted() {
synchronized (lock) {
limitingInProgress++;
keyAgreementInProgress = true;
}
LOG.info("Limiting started");
LOG.info("Key agreement started");
eventBus.broadcast(new CloseSyncConnectionsEvent(ID));
}
@Override
public void endLimiting() {
public void keyAgreementEnded() {
synchronized (lock) {
limitingInProgress--;
if (limitingInProgress < 0) {
throw new IllegalStateException();
}
keyAgreementInProgress = false;
}
LOG.info("Limiting ended");
LOG.info("Key agreement ended");
}
@Override
public boolean canOpenContactConnection() {
synchronized (lock) {
if (limitingInProgress > 0) {
LOG.info("Can't open contact connection while limiting");
if (keyAgreementInProgress) {
LOG.info("Can't open contact connection during key agreement");
return false;
} else {
LOG.info("Can open contact connection");

View File

@@ -11,10 +11,6 @@ public interface BluetoothPlugin extends DuplexPlugin {
boolean isDiscovering();
void disablePolling();
void enablePolling();
@Nullable
DuplexTransportConnection discoverAndConnectForSetup(String uuid);

View File

@@ -0,0 +1,114 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@NotNullByDefault
abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
private static final Logger LOG =
getLogger(AbstractRemovableDrivePlugin.class.getName());
private final int maxLatency;
abstract InputStream openInputStream(TransportProperties p)
throws IOException;
abstract OutputStream openOutputStream(TransportProperties p)
throws IOException;
AbstractRemovableDrivePlugin(int maxLatency) {
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Unused for simplex transports
throw new UnsupportedOperationException();
}
@Override
public void start() {
}
@Override
public void stop() {
}
@Override
public State getState() {
return ACTIVE;
}
@Override
public int getReasonsDisabled() {
return 0;
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override
public TransportConnectionReader createReader(TransportProperties p) {
try {
return new TransportInputStreamReader(openInputStream(p));
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
@Override
public TransportConnectionWriter createWriter(TransportProperties p) {
try {
return new TransportOutputStreamWriter(this, openOutputStream(p));
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
}

View File

@@ -15,8 +15,8 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;

View File

@@ -0,0 +1,83 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
@ThreadSafe
@NotNullByDefault
class RemovableDriveManagerImpl
implements RemovableDriveManager, RemovableDriveTaskRegistry {
private final Executor ioExecutor;
private final RemovableDriveTaskFactory taskFactory;
private final ConcurrentHashMap<ContactId, RemovableDriveTask>
readers = new ConcurrentHashMap<>();
private final ConcurrentHashMap<ContactId, RemovableDriveTask>
writers = new ConcurrentHashMap<>();
@Inject
RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor,
RemovableDriveTaskFactory taskFactory) {
this.ioExecutor = ioExecutor;
this.taskFactory = taskFactory;
}
@Nullable
@Override
public RemovableDriveTask getCurrentReaderTask(ContactId c) {
return readers.get(c);
}
@Nullable
@Override
public RemovableDriveTask getCurrentWriterTask(ContactId c) {
return writers.get(c);
}
@Override
public RemovableDriveTask startReaderTask(ContactId c,
TransportProperties p) {
RemovableDriveTask task = taskFactory.createReader(this, c, p);
RemovableDriveTask old = readers.putIfAbsent(c, task);
if (old == null) {
ioExecutor.execute(task);
return task;
} else {
return old;
}
}
@Override
public RemovableDriveTask startWriterTask(ContactId c,
TransportProperties p) {
RemovableDriveTask task = taskFactory.createWriter(this, c, p);
RemovableDriveTask old = writers.putIfAbsent(c, task);
if (old == null) {
ioExecutor.execute(task);
return task;
} else {
return old;
}
}
@Override
public void removeReader(ContactId c, RemovableDriveTask task) {
readers.remove(c, task);
}
@Override
public void removeWriter(ContactId c, RemovableDriveTask task) {
writers.remove(c, task);
}
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class RemovableDriveModule {
@Provides
@Singleton
RemovableDriveManager provideRemovableDriveManager(
RemovableDriveManagerImpl removableDriveManager) {
return removableDriveManager;
}
@Provides
RemovableDriveTaskFactory provideTaskFactory(
RemovableDriveTaskFactoryImpl taskFactory) {
return taskFactory;
}
}

View File

@@ -0,0 +1,38 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_PATH;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class RemovableDrivePlugin extends AbstractRemovableDrivePlugin {
RemovableDrivePlugin(int maxLatency) {
super(maxLatency);
}
@Override
InputStream openInputStream(TransportProperties p) throws IOException {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
return new FileInputStream(path);
}
@Override
OutputStream openOutputStream(TransportProperties p) throws IOException {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
return new FileOutputStream(path);
}
}

View File

@@ -0,0 +1,41 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@Immutable
@NotNullByDefault
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
@Inject
RemovableDrivePluginFactory() {
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new RemovableDrivePlugin(MAX_LATENCY);
}
}

View File

@@ -0,0 +1,88 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@NotNullByDefault
class RemovableDriveReaderTask extends RemovableDriveTaskImpl
implements EventListener {
private final static Logger LOG =
getLogger(RemovableDriveReaderTask.class.getName());
RemovableDriveReaderTask(
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
ContactId contactId,
TransportProperties transportProperties) {
super(eventExecutor, pluginManager, connectionManager, eventBus,
registry, contactId, transportProperties);
}
@Override
public void run() {
TransportConnectionReader r =
getPlugin().createReader(transportProperties);
if (r == null) {
LOG.warning("Failed to create reader");
registry.removeReader(contactId, this);
setSuccess(false);
return;
}
eventBus.addListener(this);
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessageAddedEvent) {
MessageAddedEvent m = (MessageAddedEvent) e;
if (contactId.equals(m.getContactId())) {
LOG.info("Message received");
addDone(m.getMessage().getRawLength());
}
}
}
private class DecoratedReader implements TransportConnectionReader {
private final TransportConnectionReader delegate;
private DecoratedReader(TransportConnectionReader delegate) {
this.delegate = delegate;
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public void dispose(boolean exception, boolean recognised)
throws IOException {
delegate.dispose(exception, recognised);
registry.removeReader(contactId, RemovableDriveReaderTask.this);
eventBus.removeListener(RemovableDriveReaderTask.this);
setSuccess(!exception && recognised);
}
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
@NotNullByDefault
interface RemovableDriveTaskFactory {
RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p);
RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p);
}

View File

@@ -0,0 +1,55 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory {
private final DatabaseComponent db;
private final Executor eventExecutor;
private final PluginManager pluginManager;
private final ConnectionManager connectionManager;
private final EventBus eventBus;
@Inject
RemovableDriveTaskFactoryImpl(
DatabaseComponent db,
@EventExecutor Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus) {
this.db = db;
this.eventExecutor = eventExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.eventBus = eventBus;
}
@Override
public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p) {
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
connectionManager, eventBus, registry, c, p);
}
@Override
public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p) {
return new RemovableDriveWriterTask(db, eventExecutor, pluginManager,
connectionManager, eventBus, registry, c, p);
}
}

View File

@@ -0,0 +1,120 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Math.min;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@ThreadSafe
@NotNullByDefault
abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
private final Executor eventExecutor;
private final PluginManager pluginManager;
final ConnectionManager connectionManager;
final EventBus eventBus;
final RemovableDriveTaskRegistry registry;
final ContactId contactId;
final TransportProperties transportProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private final List<Consumer<State>> observers = new ArrayList<>();
@GuardedBy("lock")
private State state = new State(0, 0, false, false);
RemovableDriveTaskImpl(
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
ContactId contactId,
TransportProperties transportProperties) {
this.eventExecutor = eventExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.eventBus = eventBus;
this.registry = registry;
this.contactId = contactId;
this.transportProperties = transportProperties;
}
@Override
public TransportProperties getTransportProperties() {
return transportProperties;
}
@Override
public void addObserver(Consumer<State> o) {
State state;
synchronized (lock) {
observers.add(o);
state = this.state;
}
if (state.isFinished()) {
eventExecutor.execute(() -> o.accept(state));
}
}
@Override
public void removeObserver(Consumer<State> o) {
synchronized (lock) {
observers.remove(o);
}
}
SimplexPlugin getPlugin() {
return (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
}
void setTotal(long total) {
synchronized (lock) {
state = new State(state.getDone(), total, state.isFinished(),
state.isSuccess());
notifyObservers();
}
}
void addDone(long done) {
synchronized (lock) {
// Done and total come from different sources; make them consistent
done = min(state.getDone() + done, state.getTotal());
state = new State(done, state.getTotal(), state.isFinished(),
state.isSuccess());
}
notifyObservers();
}
void setSuccess(boolean success) {
synchronized (lock) {
state = new State(state.getDone(), state.getTotal(), true, success);
}
notifyObservers();
}
@GuardedBy("lock")
private void notifyObservers() {
List<Consumer<State>> observers = new ArrayList<>(this.observers);
State state = this.state;
eventExecutor.execute(() -> {
for (Consumer<State> o : observers) o.accept(state);
});
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
@NotNullByDefault
interface RemovableDriveTaskRegistry {
void removeReader(ContactId c, RemovableDriveTask task);
void removeWriter(ContactId c, RemovableDriveTask task);
}

View File

@@ -0,0 +1,120 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
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.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class RemovableDriveWriterTask extends RemovableDriveTaskImpl
implements EventListener {
private static final Logger LOG =
getLogger(RemovableDriveWriterTask.class.getName());
private final DatabaseComponent db;
RemovableDriveWriterTask(
DatabaseComponent db,
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
ContactId contactId,
TransportProperties transportProperties) {
super(eventExecutor, pluginManager, connectionManager, eventBus,
registry, contactId, transportProperties);
this.db = db;
}
@Override
public void run() {
SimplexPlugin plugin = getPlugin();
TransportConnectionWriter w = plugin.createWriter(transportProperties);
if (w == null) {
LOG.warning("Failed to create writer");
registry.removeWriter(contactId, this);
setSuccess(false);
return;
}
int maxLatency = plugin.getMaxLatency();
try {
setTotal(db.transactionWithResult(true, txn ->
db.getMessageBytesToSend(txn, contactId, maxLatency)));
} catch (DbException e) {
logException(LOG, WARNING, e);
registry.removeWriter(contactId, this);
setSuccess(false);
return;
}
eventBus.addListener(this);
connectionManager.manageOutgoingConnection(contactId, ID,
new DecoratedWriter(w));
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessagesSentEvent) {
MessagesSentEvent m = (MessagesSentEvent) e;
if (contactId.equals(m.getContactId())) {
if (LOG.isLoggable(INFO)) {
LOG.info(m.getMessageIds().size() + " messages sent");
}
addDone(m.getTotalLength());
}
}
}
private class DecoratedWriter implements TransportConnectionWriter {
private final TransportConnectionWriter delegate;
private DecoratedWriter(TransportConnectionWriter delegate) {
this.delegate = delegate;
}
@Override
public int getMaxLatency() {
return delegate.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return delegate.getMaxIdleTime();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public void dispose(boolean exception) throws IOException {
delegate.dispose(exception);
registry.removeWriter(contactId, RemovableDriveWriterTask.this);
eventBus.removeListener(RemovableDriveWriterTask.this);
setSuccess(!exception);
}
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import java.io.InputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class TransportInputStreamReader implements TransportConnectionReader {
private static final Logger LOG =
getLogger(TransportInputStreamReader.class.getName());
private final InputStream in;
TransportInputStreamReader(InputStream in) {
this.in = in;
}
@Override
public InputStream getInputStream() {
return in;
}
@Override
public void dispose(boolean exception, boolean recognised) {
tryToClose(in, LOG, WARNING);
}
}

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class TransportOutputStreamWriter implements TransportConnectionWriter {
private static final Logger LOG =
getLogger(TransportOutputStreamWriter.class.getName());
private final Plugin plugin;
private final OutputStream out;
TransportOutputStreamWriter(Plugin plugin, OutputStream out) {
this.plugin = plugin;
this.out = out;
}
@Override
public int getMaxLatency() {
return plugin.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return plugin.getMaxIdleTime();
}
@Override
public OutputStream getOutputStream() {
return out;
}
@Override
public void dispose(boolean exception) {
tryToClose(out, LOG, WARNING);
}
}

View File

@@ -1,3 +1,6 @@
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.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

View File

@@ -298,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception {
context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not)
exactly(18).of(database).startTransaction();
exactly(19).of(database).startTransaction();
will(returnValue(txn));
exactly(18).of(database).containsContact(txn, contactId);
exactly(19).of(database).containsContact(txn, contactId);
will(returnValue(false));
exactly(18).of(database).abortTransaction(txn);
exactly(19).of(database).abortTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
@@ -349,7 +349,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(false, transaction ->
db.transaction(true, transaction ->
db.getContact(transaction, contactId));
fail();
} catch (NoSuchContactException expected) {
@@ -357,7 +357,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(false, transaction ->
db.transaction(true, transaction ->
db.getMessageBytesToSend(transaction, contactId, 123));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.getMessageStatus(transaction, contactId, groupId));
fail();
} catch (NoSuchContactException expected) {
@@ -365,7 +373,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(false, transaction ->
db.transaction(true, transaction ->
db.getMessageStatus(transaction, contactId, messageId));
fail();
} catch (NoSuchContactException expected) {
@@ -373,7 +381,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(false, transaction ->
db.transaction(true, transaction ->
db.getGroupVisibility(transaction, contactId, groupId));
fail();
} catch (NoSuchContactException expected) {
@@ -381,7 +389,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
try {
db.transaction(false, transaction ->
db.transaction(true, transaction ->
db.getSyncVersions(transaction, contactId));
fail();
} catch (NoSuchContactException expected) {

View File

@@ -227,6 +227,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Changing the status to seen = true should make the message unsendable
db.raiseSeenFlag(txn, contactId, messageId);
@@ -234,6 +236,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
db.commitTransaction(txn);
db.close();
@@ -258,6 +261,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Marking the message delivered should make it sendable
db.setMessageState(txn, messageId, DELIVERED);
@@ -265,6 +269,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Marking the message invalid should make it unsendable
db.setMessageState(txn, messageId, INVALID);
@@ -272,6 +278,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Marking the message pending should make it unsendable
db.setMessageState(txn, messageId, PENDING);
@@ -279,6 +286,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
db.commitTransaction(txn);
db.close();
@@ -302,6 +310,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Making the group visible should not make the message sendable
db.addGroupVisibility(txn, contactId, groupId, false);
@@ -309,6 +318,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Sharing the group should make the message sendable
db.setGroupVisibility(txn, contactId, groupId, true);
@@ -316,6 +326,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Unsharing the group should make the message unsendable
db.setGroupVisibility(txn, contactId, groupId, false);
@@ -323,6 +335,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Making the group invisible should make the message unsendable
db.removeGroupVisibility(txn, contactId, groupId);
@@ -330,6 +343,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
db.commitTransaction(txn);
db.close();
@@ -354,6 +368,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// Sharing the message should make it sendable
db.setMessageShared(txn, messageId, true);
@@ -361,6 +376,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
db.commitTransaction(txn);
db.close();
@@ -384,10 +401,15 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(message.getRawLength(),
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
// The message is just the right size to send
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
db.commitTransaction(txn);
db.close();

View File

@@ -0,0 +1,168 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertTrue;
public class RemovableDriveIntegrationTest extends BrambleTestCase {
private static final int TIMEOUT_MS = 5_000;
private final File testDir = getTestDirectory();
private final File aliceDir = new File(testDir, "alice");
private final File bobDir = new File(testDir, "bob");
private final SecretKey rootKey = getSecretKey();
private final long timestamp = System.currentTimeMillis();
private RemovableDriveIntegrationTestComponent alice, bob;
@Before
public void setUp() {
assertTrue(testDir.mkdirs());
alice = DaggerRemovableDriveIntegrationTestComponent.builder()
.testDatabaseConfigModule(
new TestDatabaseConfigModule(aliceDir)).build();
RemovableDriveIntegrationTestComponent.Helper
.injectEagerSingletons(alice);
bob = DaggerRemovableDriveIntegrationTestComponent.builder()
.testDatabaseConfigModule(
new TestDatabaseConfigModule(bobDir)).build();
RemovableDriveIntegrationTestComponent.Helper
.injectEagerSingletons(bob);
}
@Test
public void testWriteAndRead() throws Exception {
// Create the identities
Identity aliceIdentity =
alice.getIdentityManager().createIdentity("Alice");
Identity bobIdentity = bob.getIdentityManager().createIdentity("Bob");
// Set up the devices and get the contact IDs
ContactId bobId = setUp(alice, aliceIdentity,
bobIdentity.getLocalAuthor(), true);
ContactId aliceId = setUp(bob, bobIdentity,
aliceIdentity.getLocalAuthor(), false);
// Sync Alice's client versions and transport properties
read(bob, aliceId, write(alice, bobId), 2);
// Sync Bob's client versions and transport properties
read(alice, bobId, write(bob, aliceId), 2);
}
private ContactId setUp(RemovableDriveIntegrationTestComponent device,
Identity local, Author remote, boolean alice) throws Exception {
// Add an identity for the user
IdentityManager identityManager = device.getIdentityManager();
identityManager.registerIdentity(local);
// Start the lifecycle manager
LifecycleManager lifecycleManager = device.getLifecycleManager();
lifecycleManager.startServices(getSecretKey());
lifecycleManager.waitForStartup();
// Add the other user as a contact
ContactManager contactManager = device.getContactManager();
return contactManager.addContact(remote, local.getId(), rootKey,
timestamp, alice, true, true);
}
@SuppressWarnings("SameParameterValue")
private void read(RemovableDriveIntegrationTestComponent device,
ContactId contactId, File file, int deliveries) throws Exception {
// Listen for message deliveries
MessageDeliveryListener listener =
new MessageDeliveryListener(deliveries);
device.getEventBus().addListener(listener);
// Read the incoming stream
TransportProperties p = new TransportProperties();
p.put(PROP_PATH, file.getAbsolutePath());
RemovableDriveTask reader = device.getRemovableDriveManager()
.startReaderTask(contactId, p);
CountDownLatch disposedLatch = new CountDownLatch(1);
reader.addObserver(state -> {
if (state.isFinished()) disposedLatch.countDown();
});
// Wait for the messages to be delivered
assertTrue(listener.delivered.await(TIMEOUT_MS, MILLISECONDS));
// Clean up the listener
device.getEventBus().removeListener(listener);
// Wait for the reader to be disposed
disposedLatch.await(TIMEOUT_MS, MILLISECONDS);
}
private File write(RemovableDriveIntegrationTestComponent device,
ContactId contactId) throws Exception {
// Write the outgoing stream to a file
File file = File.createTempFile("sync", ".tmp", testDir);
TransportProperties p = new TransportProperties();
p.put(PROP_PATH, file.getAbsolutePath());
RemovableDriveTask writer = device.getRemovableDriveManager()
.startWriterTask(contactId, p);
CountDownLatch disposedLatch = new CountDownLatch(1);
writer.addObserver(state -> {
if (state.isFinished()) disposedLatch.countDown();
});
// Wait for the writer to be disposed
disposedLatch.await(TIMEOUT_MS, MILLISECONDS);
// Return the file containing the stream
return file;
}
private void tearDown(RemovableDriveIntegrationTestComponent device)
throws Exception {
// Stop the lifecycle manager
LifecycleManager lifecycleManager = device.getLifecycleManager();
lifecycleManager.stopServices();
lifecycleManager.waitForShutdown();
}
@After
public void tearDown() throws Exception {
// Tear down the devices
tearDown(alice);
tearDown(bob);
deleteTestDirectory(testDir);
}
@NotNullByDefault
private static class MessageDeliveryListener implements EventListener {
private final CountDownLatch delivered;
private MessageDeliveryListener(int deliveries) {
delivered = new CountDownLatch(deliveries);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
if (m.getState().equals(DELIVERED)) delivered.countDown();
}
}
}
}

View File

@@ -0,0 +1,53 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
import org.briarproject.bramble.event.DefaultEventExecutorModule;
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestSecureRandomModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
BrambleCoreModule.class,
DefaultBatteryManagerModule.class,
DefaultEventExecutorModule.class,
DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class,
RemovableDriveIntegrationTestModule.class,
RemovableDriveModule.class,
TestSecureRandomModule.class,
TimeTravelModule.class
})
interface RemovableDriveIntegrationTestComponent
extends BrambleCoreEagerSingletons {
ContactManager getContactManager();
EventBus getEventBus();
IdentityManager getIdentityManager();
LifecycleManager getLifecycleManager();
RemovableDriveManager getRemovableDriveManager();
class Helper {
public static void injectEagerSingletons(
RemovableDriveIntegrationTestComponent c) {
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
}
}
}

View File

@@ -1,61 +1,81 @@
package org.briarproject.bramble.plugin;
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
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;
@Module
public class DesktopPluginModule extends PluginModule {
class RemovableDriveIntegrationTestModule {
@Provides
PluginConfig getPluginConfig(JavaBluetoothPluginFactory bluetooth,
ModemPluginFactory modem, LanTcpPluginFactory lan,
WanTcpPluginFactory wan) {
@Singleton
PluginConfig providePluginConfig(RemovableDrivePluginFactory drive) {
@NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() {
@Override
public Collection<DuplexPluginFactory> getDuplexFactories() {
return asList(bluetooth, modem, lan, wan);
}
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return emptyList();
}
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return singletonList(drive);
}
@Override
public boolean shouldPoll() {
return true;
return false;
}
@Override
public Map<TransportId, List<TransportId>> getTransportPreferences() {
// Prefer LAN to Bluetooth
return singletonMap(BluetoothConstants.ID,
singletonList(LanTcpConstants.ID));
return emptyMap();
}
};
return pluginConfig;
}
@Provides
FeatureFlags provideFeatureFlags() {
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return true;
}
@Override
public boolean shouldEnableProfilePictures() {
return true;
}
@Override
public boolean shouldEnableDisappearingMessages() {
return true;
}
@Override
public boolean shouldEnableConnectViaBluetooth() {
return true;
}
};
}
}

View File

@@ -55,7 +55,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
private final File torDirectory;
@Inject
public UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
@IoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,

View File

@@ -26,8 +26,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 10304
versionName "1.3.4"
versionCode 10303
versionName "1.3.3"
applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true

View File

@@ -104,7 +104,8 @@
<activity
android:name="org.briarproject.briar.android.account.SetupActivity"
android:label="@string/setup_title" />
android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
<activity
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
@@ -436,6 +437,15 @@
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.removabledrive.RemovableDriveActivity"
android:label="TODO Removable Drive"
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
<activity
android:name=".android.contact.add.remote.PendingContactListActivity"
android:label="@string/pending_contact_requests"

View File

@@ -28,6 +28,7 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
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.file.RemovableDriveModule;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreEagerSingletons;
@@ -83,7 +84,8 @@ import dagger.Component;
AppModule.class,
AttachmentModule.class,
ClockModule.class,
MediaModule.class
MediaModule.class,
RemovableDriveModule.class
})
public interface AndroidComponent
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,

View File

@@ -24,6 +24,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.file.AndroidRemovableDrivePluginFactory;
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils;
@@ -67,7 +69,6 @@ import dagger.Provides;
import static android.content.Context.MODE_PRIVATE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
@@ -92,6 +93,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
GroupListModule.class,
GroupConversationModule.class,
SharingModule.class,
RemovableDriveModule.class
})
public class AppModule {
@@ -149,8 +151,10 @@ public class AppModule {
}
@Provides
@Singleton
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan) {
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
AndroidRemovableDrivePluginFactory drive) {
@NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() {
@@ -161,7 +165,7 @@ public class AppModule {
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return emptyList();
return singletonList(drive);
}
@Override

View File

@@ -32,7 +32,6 @@ public class DozeFragment extends SetupFragment
private DozeView dozeView;
private HuaweiProtectedAppsView huaweiProtectedAppsView;
private HuaweiAppLaunchView huaweiAppLaunchView;
private XiaomiView xiaomiView;
private Button next;
private boolean secondAttempt = false;
@@ -54,8 +53,6 @@ public class DozeFragment extends SetupFragment
huaweiProtectedAppsView.setOnCheckedChangedListener(this);
huaweiAppLaunchView = v.findViewById(R.id.huaweiAppLaunchView);
huaweiAppLaunchView.setOnCheckedChangedListener(this);
xiaomiView = v.findViewById(R.id.xiaomiView);
xiaomiView.setOnCheckedChangedListener(this);
next = v.findViewById(R.id.next);
ProgressBar progressBar = v.findViewById(R.id.progress);
@@ -101,8 +98,7 @@ public class DozeFragment extends SetupFragment
public void onCheckedChanged() {
next.setEnabled(dozeView.isChecked() &&
huaweiProtectedAppsView.isChecked() &&
huaweiAppLaunchView.isChecked() &&
xiaomiView.isChecked());
huaweiAppLaunchView.isChecked());
}
@SuppressLint("BatteryLife")

View File

@@ -10,7 +10,6 @@ class DozeHelperImpl implements DozeHelper {
Context appContext = context.getApplicationContext();
return needsDozeWhitelisting(appContext) ||
HuaweiProtectedAppsView.needsToBeShown(appContext) ||
HuaweiAppLaunchView.needsToBeShown(appContext) ||
XiaomiView.isXiaomiOrRedmiDevice();
HuaweiAppLaunchView.needsToBeShown(appContext);
}
}

View File

@@ -118,7 +118,7 @@ public class SetPasswordFragment extends SetupFragment {
@Override
public void onClick(View view) {
IBinder token = passwordEntry.getWindowToken();
Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE);
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
viewModel.setPassword(passwordEntry.getText().toString());
}

View File

@@ -26,8 +26,6 @@ import static org.briarproject.briar.android.account.SetupViewModel.State.CREATE
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
import static org.briarproject.briar.android.util.UiUtils.setInputStateAlwaysVisible;
import static org.briarproject.briar.android.util.UiUtils.setInputStateHidden;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -57,13 +55,10 @@ public class SetupActivity extends BaseActivity
private void onStateChanged(SetupViewModel.State state) {
if (state == AUTHOR_NAME) {
setInputStateAlwaysVisible(this);
showInitialFragment(AuthorNameFragment.newInstance());
} else if (state == SET_PASSWORD) {
setInputStateAlwaysVisible(this);
showPasswordFragment();
} else if (state == DOZE) {
setInputStateHidden(this);
showDozeFragment();
} else if (state == CREATED || state == FAILED) {
// TODO: Show an error if failed

View File

@@ -1,74 +0,0 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import android.util.AttributeSet;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import javax.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import static android.os.Build.BRAND;
import static org.briarproject.bramble.util.AndroidUtils.getSystemProperty;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@UiThread
@NotNullByDefault
class XiaomiView extends PowerView {
public XiaomiView(Context context) {
this(context, null);
}
public XiaomiView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public XiaomiView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setText(R.string.setup_xiaomi_text);
setButtonText(R.string.setup_xiaomi_button);
}
@Override
public boolean needsToBeShown() {
return isXiaomiOrRedmiDevice();
}
public static boolean isXiaomiOrRedmiDevice() {
return "Xiaomi".equalsIgnoreCase(BRAND) ||
"Redmi".equalsIgnoreCase(BRAND);
}
@Override
@StringRes
protected int getHelpText() {
return R.string.setup_xiaomi_help;
}
@Override
protected void onButtonClick() {
int bodyRes = isMiuiTenOrLater()
? R.string.setup_xiaomi_dialog_body_new
: R.string.setup_xiaomi_dialog_body_old;
showOnboardingDialog(getContext(), getContext().getString(bodyRes));
setChecked(true);
}
private boolean isMiuiTenOrLater() {
String version = getSystemProperty("ro.miui.ui.version.name");
if (isNullOrEmpty(version)) return false;
version = version.replaceAll("[^\\d]", "");
try {
return Integer.parseInt(version) >= 10;
} catch (NumberFormatException e) {
return false;
}
}
}

View File

@@ -63,6 +63,7 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment;
import org.briarproject.briar.android.removabledrive.RemovableDriveActivity;
import org.briarproject.briar.android.reporting.CrashFragment;
import org.briarproject.briar.android.reporting.CrashReportActivity;
import org.briarproject.briar.android.reporting.ReportFormFragment;
@@ -176,6 +177,8 @@ public interface ActivityComponent {
void inject(CrashReportActivity crashReportActivity);
void inject(RemovableDriveActivity activity);
// Fragments
void inject(SetupFragment fragment);

View File

@@ -14,5 +14,7 @@ public interface RequestCodes {
int REQUEST_ATTACH_IMAGE = 13;
int REQUEST_SAVE_ATTACHMENT = 14;
int REQUEST_AVATAR_IMAGE = 15;
int REQUEST_REMOVABLE_DRIVE_WRITE = 16;
int REQUEST_REMOVABLE_DRIVE_READ = 17;
}

View File

@@ -106,11 +106,9 @@ public class FeedFragment extends BaseFragment
adapter.submitList(update.getItems(), () -> {
Boolean wasLocal = update.getPostAddedWasLocal();
if (wasLocal != null && wasLocal) {
showSnackBar(R.string.blogs_blog_post_created, true);
// automatically scroll to our new post
list.smoothScrollToPosition(0);
showSnackBar(R.string.blogs_blog_post_created);
} else if (wasLocal != null) {
showSnackBar(R.string.blogs_blog_post_received, false);
showSnackBar(R.string.blogs_blog_post_received);
}
viewModel.resetLocalUpdate();
list.showData();
@@ -172,12 +170,12 @@ public class FeedFragment extends BaseFragment
return i;
}
private void showSnackBar(int stringRes, boolean isLocal) {
private void showSnackBar(int stringRes) {
int firstVisible =
layoutManager.findFirstCompletelyVisibleItemPosition();
int lastVisible = layoutManager.findLastCompletelyVisibleItemPosition();
int count = adapter.getItemCount();
boolean scroll = !isLocal && count > (lastVisible - firstVisible + 1);
boolean scroll = count > (lastVisible - firstVisible + 1);
BriarSnackbarBuilder sb = new BriarSnackbarBuilder();
if (scroll) {

View File

@@ -176,7 +176,6 @@ class BluetoothConnecter implements EventListener {
connect();
}
@UiThread
@Override
public void eventOccurred(@NonNull Event e) {
if (e instanceof ConnectionOpenedEvent) {
@@ -193,51 +192,47 @@ class BluetoothConnecter implements EventListener {
}
private void connect() {
bluetoothPlugin.disablePolling();
pluginManager.setPluginEnabled(ID, true);
ioExecutor.execute(() -> {
if (!waitForBluetoothActive()) {
showToast(R.string.bt_plugin_status_inactive);
LOG.warning("Bluetooth plugin didn't become active");
return;
}
showToast(R.string.toast_connect_via_bluetooth_start);
eventBus.addListener(this);
try {
if (!waitForBluetoothActive()) {
showToast(R.string.bt_plugin_status_inactive);
LOG.warning("Bluetooth plugin didn't become active");
String uuid = null;
try {
uuid = transportPropertyManager
.getRemoteProperties(contactId, ID).get(PROP_UUID);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
if (isNullOrEmpty(uuid)) {
LOG.warning("PROP_UUID missing for contact");
return;
}
showToast(R.string.toast_connect_via_bluetooth_start);
eventBus.addListener(this);
try {
String uuid = null;
try {
uuid = transportPropertyManager
.getRemoteProperties(contactId, ID)
.get(PROP_UUID);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
if (isNullOrEmpty(uuid)) {
LOG.warning("PROP_UUID missing for contact");
return;
}
DuplexTransportConnection conn = bluetoothPlugin
.discoverAndConnectForSetup(uuid);
if (conn == null) {
waitAfterConnectionFailed();
DuplexTransportConnection conn = bluetoothPlugin
.discoverAndConnectForSetup(uuid);
if (conn == null) {
if (!isConnectedViaBluetooth(contactId)) {
LOG.warning("Failed to connect");
showToast(R.string.toast_connect_via_bluetooth_error);
} else {
LOG.info("Could connect, handling connection");
connectionManager
.manageOutgoingConnection(contactId, ID, conn);
showToast(R.string.toast_connect_via_bluetooth_success);
LOG.info("Failed to connect, but contact connected");
}
} finally {
eventBus.removeListener(this);
return;
}
connectionManager.manageOutgoingConnection(contactId, ID, conn);
showToast(R.string.toast_connect_via_bluetooth_success);
} finally {
bluetoothPlugin.enablePolling();
eventBus.removeListener(this);
}
});
}
@IoExecutor
private boolean waitForBluetoothActive() {
long left = BT_ACTIVE_TIMEOUT;
final long sleep = 250;
@@ -255,31 +250,6 @@ class BluetoothConnecter implements EventListener {
return (bluetoothPlugin.getState() == ACTIVE);
}
/**
* Wait for an incoming connection before showing an error Toast.
*/
@IoExecutor
private void waitAfterConnectionFailed() {
long left = BT_ACTIVE_TIMEOUT;
final long sleep = 250;
try {
while (left > 0) {
if (isConnectedViaBluetooth(contactId)) {
LOG.info("Failed to connect, but contact connected");
// no Toast needed here, as it gets shown when
// ConnectionOpenedEvent is received
return;
}
Thread.sleep(sleep);
left -= sleep;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
LOG.warning("Failed to connect");
showToast(R.string.toast_connect_via_bluetooth_error);
}
private void showToast(@StringRes int res) {
androidExecutor.runOnUiThread(() ->
Toast.makeText(app, res, Toast.LENGTH_LONG).show()

View File

@@ -34,7 +34,6 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.sync.ClientId;
@@ -54,6 +53,7 @@ import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.removabledrive.RemovableDriveActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.view.ImagePreview;
@@ -421,6 +421,11 @@ public class ConversationActivity extends BriarActivity
} else if (itemId == R.id.action_social_remove_person) {
askToRemoveContact();
return true;
} else if (itemId == R.id.action_removable_drive_write) {
Intent intent = new Intent(this, RemovableDriveActivity.class);
intent.putExtra(CONTACT_ID, contactId.getInt());
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}

View File

@@ -0,0 +1,174 @@
package org.briarproject.briar.android.removabledrive;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.State;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
// TODO 19 will be our requirement for sneakernet support, right. The file apis
// used require this.
@RequiresApi(api = 19)
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class RemovableDriveActivity extends BriarActivity
implements BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private RemovableDriveViewModel viewModel;
private TextView text;
private Button writeButton;
private Button readButton;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(RemovableDriveViewModel.class);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_removable_drive);
text = findViewById(R.id.sneaker_text);
writeButton = findViewById(R.id.sneaker_write);
readButton = findViewById(R.id.sneaker_read);
Intent intent = getIntent();
int contactId = intent.getIntExtra(CONTACT_ID, -1);
if (contactId == -1) {
writeButton.setEnabled(false);
readButton.setEnabled(false);
return;
}
// TODO we can pass an extra named DocumentsContract.EXTRA_INITIAL_URI
// to have the filepicker start on the usb-stick -- if get hold of URI
// of the same. USB manager API?
// Overall, passing this extra requires extending the ready-made
// contracts and overriding createIntent.
writeButton.setText("Write for contactId " + contactId);
ActivityResultLauncher<String> createDocument =
registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
uri -> write(contactId, uri));
writeButton.setOnClickListener(
v -> createDocument.launch(viewModel.getFileName()));
readButton.setText("Read for contactId " + contactId);
ActivityResultLauncher<String> getContent =
registerForActivityResult(
new ActivityResultContracts.GetContent(),
uri -> read(contactId, uri));
readButton.setOnClickListener(
v -> getContent.launch("application/octet-stream"));
LiveData<State> state;
state = viewModel.ongoingWrite(new ContactId(contactId));
if (state == null) {
writeButton.setEnabled(true);
} else {
say("\nOngoing write:");
writeButton.setEnabled(false);
state.observe(this, (taskState) -> handleState("write", taskState));
}
state = viewModel.ongoingRead(new ContactId(contactId));
if (state == null) {
readButton.setEnabled(true);
} else {
say("\nOngoing read:");
readButton.setEnabled(false);
state.observe(this, (taskState) -> handleState("read", taskState));
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private void write(int contactId, @Nullable Uri uri) {
if (contactId == -1) {
throw new IllegalStateException();
}
if (uri == null) {
say("no URI picked for write");
return;
}
say("\nWriting to URI: " + uri);
writeButton.setEnabled(false);
LiveData<State> state = viewModel.write(new ContactId(contactId), uri);
state.observe(this, (taskState) -> handleState("write", taskState));
}
private void read(int contactId, @Nullable Uri uri) {
if (contactId == -1) {
throw new IllegalStateException();
}
if (uri == null) {
say("no URI picked for read");
return;
}
say("\nReading from URI: " + uri);
readButton.setEnabled(false);
LiveData<State> state = viewModel.read(new ContactId(contactId), uri);
state.observe(this, (taskState) -> handleState("read", taskState));
}
private void handleState(String action, State taskState) {
say(String.format(Locale.getDefault(),
"%s: bytes done: %d of %d. %s. %s.",
action, taskState.getDone(), taskState.getTotal(),
taskState.isFinished() ? "Finished" : "Ongoing",
taskState.isFinished() ?
(taskState.isSuccess() ? "Success" : "Failed") : ".."));
if (taskState.isFinished()) {
if (action.equals("write")) {
writeButton.setEnabled(true);
} else if (action.equals("read")) {
readButton.setEnabled(true);
}
}
}
private void say(String txt) {
String time = new SimpleDateFormat("HH:mm:ss", Locale.getDefault())
.format(new Date());
txt = String.format("%s %s\n", time, txt);
text.setText(text.getText().toString().concat(txt));
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.briar.android.removabledrive;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public interface RemovableDriveModule {
@Binds
@IntoMap
@ViewModelKey(RemovableDriveViewModel.class)
ViewModel bindRemovableDriveViewModel(RemovableDriveViewModel removableDriveViewModel);
}

View File

@@ -0,0 +1,101 @@
package org.briarproject.briar.android.removabledrive;
import android.app.Application;
import android.net.Uri;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.State;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.Locale.US;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
@NotNullByDefault
class RemovableDriveViewModel extends AndroidViewModel {
private static final Logger LOG =
getLogger(RemovableDriveViewModel.class.getName());
private final RemovableDriveManager manager;
private final ConcurrentHashMap<Consumer<State>, RemovableDriveTask>
observers = new ConcurrentHashMap<>();
@Inject
RemovableDriveViewModel(Application app,
RemovableDriveManager removableDriveManager) {
super(app);
this.manager = removableDriveManager;
}
String getFileName() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", US);
return sdf.format(new Date());
}
LiveData<State> write(ContactId contactId, Uri uri) {
TransportProperties p = new TransportProperties();
p.put(PROP_URI, uri.toString());
return observe(manager.startWriterTask(contactId, p));
}
LiveData<State> read(ContactId contactId, Uri uri) {
TransportProperties p = new TransportProperties();
p.put(PROP_URI, uri.toString());
return observe(manager.startReaderTask(contactId, p));
}
@Nullable
LiveData<State> ongoingWrite(ContactId contactId) {
RemovableDriveTask task = manager.getCurrentWriterTask(contactId);
if (task == null) {
return null;
}
return observe(task);
}
@Nullable
LiveData<State> ongoingRead(ContactId contactId) {
RemovableDriveTask task = manager.getCurrentReaderTask(contactId);
if (task == null) {
return null;
}
return observe(task);
}
private LiveData<State> observe(RemovableDriveTask task) {
MutableLiveData<State> state = new MutableLiveData<>();
Consumer<State> observer = state::postValue;
task.addObserver(observer);
observers.put(observer, task);
return state;
}
@Override
protected void onCleared() {
for (Map.Entry<Consumer<State>, RemovableDriveTask> entry
: observers.entrySet()) {
entry.getValue().removeObserver(entry.getKey());
}
}
}

View File

@@ -96,16 +96,6 @@ public class TestDataActivity extends BriarActivity {
forumPostsSeekBar.setOnSeekBarChangeListener(
new OnSeekBarChangeUpdateProgress(forumPostsTextView));
findViewById(R.id.buttonZeroValues).setOnClickListener(
v -> {
contactsSeekBar.setProgress(0);
messagesSeekBar.setProgress(0);
avatarsSeekBar.setProgress(0);
blogPostsSeekBar.setProgress(0);
forumsSeekBar.setProgress(0);
forumPostsSeekBar.setProgress(0);
});
findViewById(R.id.buttonCreateTestData).setOnClickListener(
v -> createTestData());
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -91,9 +90,6 @@ import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
import static android.view.inputmethod.EditorInfo.IME_NULL;
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
import static android.widget.Toast.LENGTH_LONG;
@@ -541,14 +537,4 @@ public class UiUtils {
Toast.makeText(context, msg, LENGTH_LONG).show();
});
}
public static void setInputStateAlwaysVisible(Activity activity) {
activity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE |
SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
public static void setInputStateHidden(Activity activity) {
activity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE |
SOFT_INPUT_STATE_HIDDEN);
}
}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".android.removabledrive.RemovableDriveActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_large">
<Button
android:id="@+id/sneaker_write"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:enabled="false"
android:text=""
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:enabled="true" />
<Button
android:id="@+id/sneaker_read"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:enabled="false"
android:text=""
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/sneaker_write"
tools:enabled="true" />
<TextView
android:id="@+id/sneaker_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/margin_large"
android:textSize="12sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/sneaker_read" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -191,19 +191,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textViewForumMessages" />
<Button
android:id="@+id/buttonZeroValues"
style="@style/BriarButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:textSize="@dimen/text_size_tiny"
android:text="Zero values"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/seekBarForumMessages"
app:layout_constraintVertical_bias="1.0" />
<Button
android:id="@+id/buttonCreateTestData"
style="@style/BriarButton"
@@ -214,7 +201,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/buttonZeroValues"
app:layout_constraintTop_toBottomOf="@+id/seekBarForumMessages"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -38,15 +38,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/huaweiProtectedAppsView" />
<org.briarproject.briar.android.account.XiaomiView
android:id="@+id/xiaomiView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_large"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/huaweiAppLaunchView" />
<Button
android:id="@+id/next"
style="@style/BriarButton"
@@ -57,7 +48,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/xiaomiView"
app:layout_constraintTop_toBottomOf="@+id/huaweiAppLaunchView"
app:layout_constraintVertical_bias="1.0"
tools:enabled="true" />

View File

@@ -40,4 +40,9 @@
android:title="@string/delete_contact"
app:showAsAction="never" />
<item
android:id="@+id/action_removable_drive_write"
android:title="Transfer via removable drive"
app:showAsAction="never" />
</menu>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">الرجاء الضغط على الزر في الأسفل والتأكد من أن Briar (براير) محمى في شاشة \"التطبيقات المحمية\".</string>
<string name="setup_huawei_button">حماية Briar (براير)</string>
<string name="setup_huawei_help">إذا لم يتم إضافة Briar (براير) في قائمة التطبيقات المحمية، فلن يتمكن من العمل في الخلفية.</string>
<string name="setup_xiaomi_button">حماية Briar (براير)</string>
<string name="warning_dozed">%s لم يتمكن من الاشتغال في الخلفية</string>
<!--Login-->
<string name="enter_password">‮كلمة السّر</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Feu un toc sobre el botó següent i assegureu-vos de que Briar consta com a protegit a la pantalla «Aplicacions protegides».</string>
<string name="setup_huawei_button">Protegeix Briar</string>
<string name="setup_huawei_help">Si no afegiu Briar a la llista d\'aplicacions protegides, s\'evitarà que Briar s\'executi en segon pla.</string>
<string name="setup_xiaomi_button">Protegeix Briar</string>
<string name="warning_dozed">%s no s\'ha pogut executar en segon pla</string>
<!--Login-->
<string name="enter_password">Contrasenya</string>

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">Bitte tippe auf die Schaltfläche unten, öffne den Bereich \"App-Start\" und stelle sicher, dass Briar auf \"Manuell verwalten\" eingestellt ist.</string>
<string name="setup_huawei_app_launch_button">Akkueinstellungen öffnen</string>
<string name="setup_huawei_app_launch_help">Wenn Briar im Bereich \"App-Start\" nicht auf \"Manuell verwalten\" eingestellt ist, kann es nicht im Hintergrund ausgeführt werden.</string>
<string name="setup_xiaomi_text">Um im Hintergrund zu laufen, muss Briar in der Liste der zuletzt verwendeten Apps gesperrt werden.</string>
<string name="setup_xiaomi_button">Briar schützen</string>
<string name="setup_xiaomi_help">Wenn Briar nicht in der Liste der zuletzt verwendeten Apps gesperrt ist, kann es nicht im Hintergrund ausgeführt werden.</string>
<string name="setup_xiaomi_dialog_body_old">1. Öffne die Liste der zuletzt verwendeten Apps (auch \'App Switcher\' genannt)\n\n2. Wische auf dem Bild von Briar nach unten, um das Vorhängeschlosssymbol anzuzeigen\n\n3. Wenn das Vorhängeschloss nicht gesperrt ist, tippe darauf, um es zu sperren</string>
<string name="setup_xiaomi_dialog_body_new">1. Öffne die Liste der zuletzt verwendeten Apps (auch \'App Switcher\' genannt)\n\n2. Halte das Bild von Briar gedrückt, bis die Schaltfläche für das Vorhängeschloss angezeigt wird\n\n3. Wenn das Vorhängeschloss nicht gesperrt ist, tippe darauf, um es zu sperren</string>
<string name="warning_dozed">%s konnte nicht im Hintergrund ausgeführt werden</string>
<!--Login-->
<string name="enter_password">Passwort</string>
@@ -353,8 +348,8 @@
<string name="groups_dissolved_dialog_message">Der Ersteller dieser Gruppe hat diese aufgelöst.\n\nEs können keine weiteren Nachrichten mehr in dieser Gruppe geschrieben werden und möglicherweise wurden noch nicht alle Nachrichten empfangen.</string>
<!--Private Group Invitations-->
<string name="groups_invitations_title">Gruppeneinladungen</string>
<string name="groups_invitations_invitation_sent">Du hast %1$s eingeladen, der Gruppe \"%2$s\" beizutreten.</string>
<string name="groups_invitations_invitation_received">%1$s hat dich eingeladen, der Gruppe \"%2$s\" beizutreten.</string>
<string name="groups_invitations_invitation_sent">%1$s wurde in die Gruppe \"%2$s\" eingeladen.</string>
<string name="groups_invitations_invitation_received">%1$s hat dich eingeladen der Gruppe \"%2$s\" beizutreten.</string>
<string name="groups_invitations_joined">Gruppe beigetreten</string>
<string name="groups_invitations_declined">Gruppeneinladung abgelehnt</string>
<plurals name="groups_invitations_open">
@@ -463,8 +458,6 @@
<string name="blogs_rss_feeds_import_button">Importieren</string>
<string name="blogs_rss_feeds_import_hint">URL des RSS-Feeds eingeben</string>
<string name="blogs_rss_feeds_import_error">Es tut uns Leid! Es gab einen Fehler beim Importieren deines Feeds.</string>
<string name="blogs_rss_feeds_import_exists">Dieser Feed ist bereits importiert.</string>
<string name="blogs_rss_feeds">RSS-Feeds</string>
<string name="blogs_rss_feeds_manage_imported">Importiert:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Letzte Aktualisierung:</string>

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">Por favor, pulsa el botón de abajo, abre la pantalla \"lanzamiento de aplicación\" y asegúrate de que Briar esté configurado como \"Gestionar manualmente\".</string>
<string name="setup_huawei_app_launch_button">Abrir Ajustes de Batería</string>
<string name="setup_huawei_app_launch_help">Si Briar no está configurado como \"Gestionar manualmente\" en la pantalla \"lanzamiento de aplicación\", no será capaz de ejecutarse en segundo plano.</string>
<string name="setup_xiaomi_text">Para ejecutarse en segundo plano, Briar necesita estar bloqueado en la lista de aplicaciones recientes.</string>
<string name="setup_xiaomi_button">Proteger Briar</string>
<string name="setup_xiaomi_help">Si Briar no está bloqueado en la lista de aplicaciones recientes, no podrá ejecutarse en segundo plano.</string>
<string name="setup_xiaomi_dialog_body_old">1. Abre la lista de aplicaciones recientes (también llamada selectora de aplicaciones)\n\n2. Desliza hacia abajo sobre la imagen de Briar para mostrar el ícono de un candado\n\n3. Si el candado no está bloqueado, púlsalo para hacerlo</string>
<string name="setup_xiaomi_dialog_body_new">1. Abre la lista de aplicaciones recientes (también llamada selectora de aplicaciones)\n\n2. Presiona y mantén la imagen de Briar hasta que aparezca el botón de un candado\n\n3. Si el candado no está bloqueado, púlsalo para hacerlo</string>
<string name="warning_dozed">%s no pudo ejecutarse en segundo plano</string>
<!--Login-->
<string name="enter_password">Contraseña</string>
@@ -463,8 +458,6 @@
<string name="blogs_rss_feeds_import_button">Importar</string>
<string name="blogs_rss_feeds_import_hint">Introduce la URL del canal RSS</string>
<string name="blogs_rss_feeds_import_error">¡Lo sentimos! Hubo un error importando tu canal.</string>
<string name="blogs_rss_feeds_import_exists">Ese canal ya está importado.</string>
<string name="blogs_rss_feeds">Canales RSS</string>
<string name="blogs_rss_feeds_manage_imported">Importado:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Última actualización:</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Sakatu beheko botoia eta ziurtatu Briar babestuta dagoela \"Babestutako aplikazioak\" pantailan.</string>
<string name="setup_huawei_button">Babestu Briar</string>
<string name="setup_huawei_help">Briar ez bada babestutako aplikazioen zerrendara gehitzen, ezin izango du bigarren planoan ibili.</string>
<string name="setup_xiaomi_button">Babestu Briar</string>
<string name="warning_dozed">%s ezin izan da bigarren planoan exekutatu</string>
<!--Login-->
<string name="enter_password">Pasahitza</string>
@@ -396,7 +395,6 @@
<string name="blogs_rss_feeds_import_button">Inportatu</string>
<string name="blogs_rss_feeds_import_hint">Sartu RSS jarioaren URLa</string>
<string name="blogs_rss_feeds_import_error">Sentitzen dugu! Zure jarioa inportatzean errore bat gertatu da.</string>
<string name="blogs_rss_feeds">RSS iturriak</string>
<string name="blogs_rss_feeds_manage_imported">Inportatuta:</string>
<string name="blogs_rss_feeds_manage_author">Egilea:</string>
<string name="blogs_rss_feeds_manage_updated">Azken eguneratzea:</string>

View File

@@ -27,7 +27,6 @@
<string name="setup_huawei_app_launch_text">لطفا روی دکمه زیر زده، صفحه‌ی \"راه اندازی برنامه\" را باز کرده و از این که برایر (Briar) بر روی \"مدیریت دستی\" تنظیم شده باشد، اطمینان حاصل کنید.</string>
<string name="setup_huawei_app_launch_button">باز کردن تنظیمات باتری</string>
<string name="setup_huawei_app_launch_help">اگر در صفحه \"راه اندازی برنامه\"، برایر (Briar) بر روی گزینه \"مدیریت دستی\" تنظیم نشده باشد، برنامه قادر به فعالیت در پس‌زمینه نخواهد بود.</string>
<string name="setup_xiaomi_button">حفاظت از Briar (برایر)</string>
<string name="warning_dozed">ناتوانی %s برای اجراء در پس زمینه</string>
<!--Login-->
<string name="enter_password">گذرواژه</string>
@@ -493,7 +492,6 @@
<string name="blogs_rss_feeds_import_button">وارد کردن</string>
<string name="blogs_rss_feeds_import_hint">آدرس خوراک RSS را وارد کنید</string>
<string name="blogs_rss_feeds_import_error">متاسفیم! وارد کردن خوراک شما با خطا مواجه شده است.</string>
<string name="blogs_rss_feeds">خوراک های RSS</string>
<string name="blogs_rss_feeds_manage_imported">وارد شده:</string>
<string name="blogs_rss_feeds_manage_author">نویسنده:</string>
<string name="blogs_rss_feeds_manage_updated">آخرین به روز رسانی:</string>

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">Veuillez toucher le bouton ci-dessous, ouvrir lécran « Lancement des applis » et vous assurer que « Gérer manuellement » est défini pour Briar.</string>
<string name="setup_huawei_app_launch_button">Ouvrez les paramètres de la pile</string>
<string name="setup_huawei_app_launch_help">Si « Gérer manuellement » nest pas défini pour Briar dans lécran « Lancement des applis », lappli ne pourra pas fonctionner en arrière-plan.</string>
<string name="setup_xiaomi_text">Pour fonctionner en arrière-plan, Briar doit être verrouillée à la liste des applis récentes.</string>
<string name="setup_xiaomi_button">Protéger Briar</string>
<string name="setup_xiaomi_help">Si Briar nest pas verrouillée à la liste des applis récentes, elle ne pourra pas fonctionner en arrière-plan.</string>
<string name="setup_xiaomi_dialog_body_old">1. Ouvrez la liste des applis récentes (aussi appelé sélecteur dappli)\n\n2. Balayez limage de Briar vers le bas pour afficher licône de verrou\n\n3. Si le verrou nest pas verrouillé, touchez pour le verrouiller</string>
<string name="setup_xiaomi_dialog_body_new">1. Ouvrez la liste des applis récentes (aussi appelé sélecteur dappli)\n\n2. Touchez et maintenez limage de Briar jusquà lapparition du verrou\n\n3. Si le verrou nest pas verrouillé, touchez pour le verrouiller</string>
<string name="warning_dozed">%s na pas pu fonctionner en arrière-plan</string>
<!--Login-->
<string name="enter_password">Mot de passe</string>
@@ -463,8 +458,6 @@
<string name="blogs_rss_feeds_import_button">Importer</string>
<string name="blogs_rss_feeds_import_hint">Saisir lURL du fil RSS</string>
<string name="blogs_rss_feeds_import_error">Nous sommes désolés! Une erreur est survenue lors de limportation de votre fil.</string>
<string name="blogs_rss_feeds_import_exists">Ce fil est déjà importé.</string>
<string name="blogs_rss_feeds">Fils RSS</string>
<string name="blogs_rss_feeds_manage_imported">Importés :</string>
<string name="blogs_rss_feeds_manage_author">Auteur :</string>
<string name="blogs_rss_feeds_manage_updated">Dernière mise à jour :</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Por favor toca o botón inferior e asegúrate de que Briar está protexida na pantalla \"Apps Protexidas\"</string>
<string name="setup_huawei_button">Protexer Briar</string>
<string name="setup_huawei_help">Se Briar non se engade ao listado de apps protexidas, non poderá funcionar en segundo plano.</string>
<string name="setup_xiaomi_button">Protexer Briar</string>
<string name="warning_dozed">%s non foi quen de funcionar en segundo plano</string>
<!--Login-->
<string name="enter_password">Contrasinal</string>
@@ -455,7 +454,6 @@
<string name="blogs_rss_feeds_import_button">Importar</string>
<string name="blogs_rss_feeds_import_hint">Escribe o URL da fonte RSS</string>
<string name="blogs_rss_feeds_import_error">Lamentámolo! Algo fallou ao importar a fonte.</string>
<string name="blogs_rss_feeds">Fontes RSS</string>
<string name="blogs_rss_feeds_manage_imported">Importado:</string>
<string name="blogs_rss_feeds_manage_author">Autor/a:</string>
<string name="blogs_rss_feeds_manage_updated">Última actualización:</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">נא להקיש על הכפתור למטה וולוודא כי Briar מוגן במסך \"יישומים מוגנים\".</string>
<string name="setup_huawei_button">הגן על Briar</string>
<string name="setup_huawei_help">אם Briar אינו מוסף אל רשימת היישומים המוגנים, הוא לא יוכל לרוץ ברקע.</string>
<string name="setup_xiaomi_button">הגן על Briar</string>
<string name="warning_dozed">%s לא היה יכול לרוץ ברקע</string>
<!--Login-->
<string name="enter_password">סיסמה</string>
@@ -445,7 +444,6 @@
<string name="blogs_rss_feeds_import_button">ייבא</string>
<string name="blogs_rss_feeds_import_hint">הכנס את כתובת האתר של הזנת ה־RSS</string>
<string name="blogs_rss_feeds_import_error">אנחנו מצטערים! הייתה שגיאה ביבוא ההזנה שלך.</string>
<string name="blogs_rss_feeds">הזנות RSS</string>
<string name="blogs_rss_feeds_manage_imported">מיובא:</string>
<string name="blogs_rss_feeds_manage_author">מחבר:</string>
<string name="blogs_rss_feeds_manage_updated">עודכן לאחרונה:</string>

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">Kérjük érintsd meg a gombot alább, hogy megnyitsd az \"App indítás\" képernyőt és ellenőrizd, hogy a Briar beállított \"Kézi kezelés\"-re.</string>
<string name="setup_huawei_app_launch_button">Az akkumulátor beállítások megnyitása</string>
<string name="setup_huawei_app_launch_help">Ha a Briar nincs beállítva \"Kézi kezelés\"-re az \"App indítás\" képernyőn, nem fog tudni futni a háttérben.</string>
<string name="setup_xiaomi_text">Ahhoz, hogy a háttérben fusson, a Briar-t rögzíteni kell a legutóbbi appok listáján.</string>
<string name="setup_xiaomi_button">A Briar védelme</string>
<string name="setup_xiaomi_help">Ha Briar nincs rögzítve a legutóbbi appok listájában, akkor nem képes futni a háttérben.</string>
<string name="setup_xiaomi_dialog_body_old">1. Nyissa meg a legutolsó appok listáját (másnéven az app váltót)\n\n2. Görgessen le a Briar képén, hogy megjelenjen a lakat ikon\n\n3. Ha a lakat nem zárt, érintse meg a lezárásához</string>
<string name="setup_xiaomi_dialog_body_new">1. Nyissa meg a legutolsó appok listáját (másnéven az app váltót)\n\n2. Nyomja meg és tartsa nyomva a Briar képét, hogy megjelenjen a lakat ikon\n\n3. Ha a lakat nem zárt, érintse meg a lezárásához</string>
<string name="warning_dozed">%s nem tud futni a háttérben</string>
<!--Login-->
<string name="enter_password">Jelszó</string>
@@ -470,8 +465,6 @@ Kapcsolatai, akivel megosztotta ezt a blogot, lehet nem kapnak többé frissít
<string name="blogs_rss_feeds_import_button">Importálás</string>
<string name="blogs_rss_feeds_import_hint">Adja meg az RSS feed URL címét</string>
<string name="blogs_rss_feeds_import_error">Elnézését kérjük! Probléma akadt a feed-je importálásával.</string>
<string name="blogs_rss_feeds_import_exists">Ez a feed már importálva van.</string>
<string name="blogs_rss_feeds">RSS Feed-ek</string>
<string name="blogs_rss_feeds_manage_imported">Importálva:</string>
<string name="blogs_rss_feeds_manage_author">Szerző:</string>
<string name="blogs_rss_feeds_manage_updated">Utolsó frissítés:</string>

View File

@@ -25,7 +25,6 @@
<string name="setup_huawei_app_launch_text">Ýttu á hnappinn hér fyrir neðan, opnaðu \"Ræsing forrits\" skjáinn og gakktu úr skugga um að Briar sé stillt á \"Stýra handvirkt\".</string>
<string name="setup_huawei_app_launch_button">Opna rafhlöðustillingar</string>
<string name="setup_huawei_app_launch_help">Ef Briar er ekki stillt á \"Stýra handvirkt\" í \"Ræsing forrits\" skjánum, mun það ekki geta keyrt í bakgrunni.</string>
<string name="setup_xiaomi_button">Verja Briar</string>
<string name="warning_dozed">%s gat ekki keyrt í bakgrunni</string>
<!--Login-->
<string name="enter_password">Lykilorð</string>
@@ -459,8 +458,6 @@
<string name="blogs_rss_feeds_import_button">Flytja inn</string>
<string name="blogs_rss_feeds_import_hint">Settu inn slóðina á RSS-streymið</string>
<string name="blogs_rss_feeds_import_error">Því miður! Það kom upp villa við að flytja inn streymið.</string>
<string name="blogs_rss_feeds_import_exists">Þegar er búið að flytja inn þetta streymi.</string>
<string name="blogs_rss_feeds">RSS-fréttastreymi</string>
<string name="blogs_rss_feeds_manage_imported">Flutt inn:</string>
<string name="blogs_rss_feeds_manage_author">Höfundur:</string>
<string name="blogs_rss_feeds_manage_updated">Síðast uppfært:</string>

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">Tocca il pulsante sotto, apri la schermata \"Esecuzione app\" e assicurati che Briar sia su \"Gestisci manualmente\".</string>
<string name="setup_huawei_app_launch_button">Apri impostazioni batteria</string>
<string name="setup_huawei_app_launch_help">Se Briar non è su \"Gestisci manualmente\" nella schermata \"Esecuzione app\", non potrà funzionare in secondo piano.</string>
<string name="setup_xiaomi_text">Per funzionare in secondo piano, Briar deve essere fissato nella lista di app recenti.</string>
<string name="setup_xiaomi_button">Proteggi Briar</string>
<string name="setup_xiaomi_help">Se Briar non è fissato nella lista di app recenti, non potrà funzionare in secondo piano.</string>
<string name="setup_xiaomi_dialog_body_old">1. Apri la lista di app recenti (chiamata anche app switcher)\n\n2. Scorri fino alla schermata di Briar per mostrare l\'icona del lucchetto\n\n3. Se il lucchetto non è chiuso, toccalo per chiuderlo</string>
<string name="setup_xiaomi_dialog_body_new">1. Apri la lista di app recenti (chiamata anche app switcher)\n\n2. Tieni premuta la schermata di Briar finché non compare l\'icona del lucchetto\n\n3. Se il lucchetto non è chiuso, toccalo per chiuderlo</string>
<string name="warning_dozed">%s non ha potuto funzionare in background</string>
<!--Login-->
<string name="enter_password">Password</string>
@@ -463,8 +458,6 @@
<string name="blogs_rss_feeds_import_button">Importa</string>
<string name="blogs_rss_feeds_import_hint">Inserire l\'URL dell\'RSS feed</string>
<string name="blogs_rss_feeds_import_error">Ci dispiace! C\'è stato un errore nell\'importazione del tuo feed.</string>
<string name="blogs_rss_feeds_import_exists">Quel flusso è già importato.</string>
<string name="blogs_rss_feeds">Flussi RSS</string>
<string name="blogs_rss_feeds_manage_imported">Importato:</string>
<string name="blogs_rss_feeds_manage_author">Autore:</string>
<string name="blogs_rss_feeds_manage_updated">Ultimo Aggiornamento:</string>

View File

@@ -22,14 +22,10 @@
<string name="setup_huawei_text">下のボタンをタップして、「保護されたアプリ」画面でBriarが保護されていることを確認してください。</string>
<string name="setup_huawei_button">Briarを保護</string>
<string name="setup_huawei_help">Briarが保護されたアプリのリストに追加されていない場合、Briarはバックグラウンドで実行することができません。</string>
<string name="setup_huawei_app_launch_button">バッテリー設定を開く</string>
<string name="setup_xiaomi_button">Briarを保護</string>
<string name="warning_dozed">%sはバックグラウンドで実行することができませんでした</string>
<!--Login-->
<string name="enter_password">パスワード</string>
<string name="try_again">パスワードが間違っています。もう一度入力してください。</string>
<string name="dialog_title_cannot_check_password">パスワードを確認できません</string>
<string name="dialog_message_cannot_check_password">Briarはあなたのパスワードを確認できません。この問題を解決するため、デバイスの再起動を試してください。</string>
<string name="sign_in_button">サインイン</string>
<string name="forgotten_password">パスワードを忘れました。</string>
<string name="dialog_title_lost_password">パスワードを紛失</string>
@@ -45,9 +41,7 @@
<item quantity="other">これは、Briarのテストバージョンです。 アカウントは%d日で期限切れになり、更新できません。</item>
</plurals>
<string name="expiry_date_reached">このソフトの有効期限が切れました。テストに参加してくださりありがとうございます!</string>
<string name="download_briar">Briarの使用を続けるならば、最新リリースをダウンロードしてください。</string>
<string name="create_new_account">新しいアカウントを作成する必要があります。同じニックネームも使用できます。</string>
<string name="download_briar_button">最新版をダウンロード</string>
<string name="startup_open_database">データベースの復号化中…</string>
<string name="startup_migrate_database">データベースをアップグレード中…</string>
<string name="startup_compact_database">データベースの圧縮中…</string>
@@ -62,36 +56,12 @@
<string name="lock_button">アプリをロック</string>
<string name="settings_button">設定</string>
<string name="sign_out_button">サインアウト</string>
<string name="transports_onboarding_text">ここにタップすると、Briarがあなたの連絡先に接続する方法を制御できます。</string>
<!--Transports: Tor-->
<string name="transport_tor">インターネット</string>
<string name="tor_device_status_online_wifi">携帯電話はWi-Fiでインターネットにアクセスできます</string>
<string name="tor_device_status_online_mobile">携帯電話はモバイル データでインターネットにアクセスできます</string>
<string name="tor_device_status_offline">携帯電話がインターネットに接続できない</string>
<string name="tor_plugin_status_enabling">Briarはインターネットに接続中</string>
<string name="tor_plugin_status_active">Briarはインターネットに接続しました</string>
<string name="tor_plugin_status_inactive">Briarはインターネットに接続不可能</string>
<string name="tor_plugin_status_disabled">Briarはインターネットを使用しないように設定されました</string>
<string name="tor_plugin_status_disabled_mobile_data">Briarはモバイルデータを使用しないように設定されました</string>
<string name="tor_plugin_status_disabled_battery">Briarはバッテリー駆動時にインターネットを使用しないように設定されました</string>
<string name="tor_plugin_status_disabled_country_blocked">Briarはこの国でインターネットを使わないように設定されました</string>
<!--Transports: Wi-Fi-->
<string name="transport_lan">Wi-Fi</string>
<string name="transport_lan_long">同じWi-Fiネットワーク</string>
<string name="lan_device_status_on">携帯電話はWi-Fiに接続されました</string>
<string name="lan_device_status_off">携帯電話はWi-Fiに接続されていません</string>
<string name="lan_plugin_status_enabling">BriarはWi-Fiネットワークに接続中</string>
<string name="lan_plugin_status_active">BriarはWi-Fiネットワークに接続されました</string>
<string name="lan_plugin_status_inactive">BriarはWi-Fiネットワークに接続不可能</string>
<string name="lan_plugin_status_disabled">BriarはWi-Fiネットワークを使用しないように設定されました</string>
<!--Transports: Bluetooth-->
<string name="transport_bt">Bluetooth</string>
<string name="bt_device_status_on">携帯電話のBluetoothはオンにされました</string>
<string name="bt_device_status_off">携帯電話のBluetoothはオフにされました</string>
<string name="bt_plugin_status_enabling">BriarはBluetoothに接続中</string>
<string name="bt_plugin_status_active">BriarはBluetoothに接続しました</string>
<string name="bt_plugin_status_inactive">BriarはBluetoothに接続不可能</string>
<string name="bt_plugin_status_disabled">BriarはBluetoothを使用しないように設定されました</string>
<!--Notifications-->
<string name="reminder_notification_title">Briarからサインアウト</string>
<string name="reminder_notification_text">タップして再ログインします。</string>
@@ -127,15 +97,14 @@
<string name="allow">許可</string>
<string name="open">開く</string>
<string name="change">変更</string>
<string name="start">開始</string>
<string name="start">スタート</string>
<string name="no_data">データなし</string>
<string name="ellipsis">...</string>
<string name="text_too_long">入力された文章が長すぎます。</string>
<string name="show_onboarding">ヘルプダイアログを表示</string>
<string name="fix">修復する</string>
<string name="help">ヘルプ</string>
<string name="sorry">申し訳ありません</string>
<string name="error_start_activity">あなたのシステム上で利用不可能</string>
<string name="sorry">すみません</string>
<string name="status_heading">状態</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">表示する連絡先がありません</string>
@@ -143,8 +112,6 @@
<string name="date_no_private_messages">メッセージがありません。</string>
<string name="no_private_messages">表示するメッセージがありません</string>
<string name="message_hint">新しいメッセージ</string>
<string name="message_hint_auto_delete">新しい消えたメッセージ</string>
<string name="message_error">メッセージ送信エラー</string>
<string name="image_caption_hint">説明文を追加する(任意)</string>
<string name="image_attach">画像を添付</string>
<string name="image_attach_error">画像を添付できませんでした</string>
@@ -152,41 +119,12 @@
<string name="image_attach_error_invalid_mime_type">サポートされていない画像形式:%s</string>
<string name="set_contact_alias">連絡先を変更</string>
<string name="set_contact_alias_hint">連絡先名</string>
<string name="menu_item_disappearing_messages">消えたメッセージ</string>
<string name="menu_item_connect_via_bluetooth">Bluetooth経由で接続する</string>
<string name="dialog_title_connect_via_bluetooth">Bluetooth経由で接続する</string>
<string name="dialog_message_connect_via_bluetooth">この機能を利用するには、あなたの連絡先が近くにある必要があります。\n\nあなたとあなたの連絡先が同時に\"開始\"を押してください。</string>
<string name="toast_connect_via_bluetooth_already_discovering">Bluetooth経由の接続を既に試行中です</string>
<string name="toast_connect_via_bluetooth_not_discoverable">Bluetoothなくして続行不可能</string>
<string name="toast_connect_via_bluetooth_no_location_permission">位置情報の権限なくして続行不可能</string>
<string name="toast_connect_via_bluetooth_start">Bluetooth経由で接続中…</string>
<string name="toast_connect_via_bluetooth_success">Bluetooth経由で接続に成功</string>
<string name="toast_connect_via_bluetooth_error">Bluetooth経由で接続不可能</string>
<!--The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more."-->
<!--The placeholder at the end will add "Tap to learn more."-->
<!--The first placeholder will show a contact's name. The second placeholder will show a duration like "7 days". The third placeholder at the end will add "Tap to learn more."-->
<plurals name="duration_minutes">
<item quantity="other">%d分</item>
</plurals>
<plurals name="duration_hours">
<item quantity="other">%d時</item>
</plurals>
<plurals name="duration_days">
<item quantity="other">%d日</item>
</plurals>
<!--The first placeholder will show a contact's name. The second placeholder at the end will add "Tap to learn more."-->
<string name="tap_to_learn_more">タップすると詳細が表示されます。</string>
<string name="auto_delete_changed_warning_send">とりあえず送る</string>
<string name="delete_all_messages">全てのメッセージを削除</string>
<string name="dialog_title_delete_all_messages">メッセージの削除時に確認</string>
<string name="dialog_message_delete_all_messages">本当に全てのメッセージを削除してもよろしいですか?</string>
<string name="dialog_title_not_all_messages_deleted">全てのメッセージを削除不可能</string>
<string name="dialog_message_not_deleted_ongoing_both">継続中の招待と紹介に関わるメッセージは、終了するまで削除できません。</string>
<string name="dialog_message_not_deleted_ongoing_introductions">継続中の紹介に関わるメッセージは、終了するまで削除できません。</string>
<string name="dialog_message_not_deleted_ongoing_invitations">継続中の招待に関わるメッセージは、終了するまで削除できません。</string>
<string name="dialog_message_not_deleted_not_all_selected_both">招待と紹介を削除するには、要求と応答を選択する必要があります。</string>
<string name="dialog_message_not_deleted_not_all_selected_introductions">紹介を削除するには、要求と応答を選択する必要があります。</string>
<string name="dialog_message_not_deleted_not_all_selected_invitations">招待を削除するには、要求と応答を選択する必要があります。</string>
<string name="delete_contact">この連絡先を削除</string>
<string name="dialog_title_delete_contact">連絡先の削除時に確認</string>
<string name="dialog_message_delete_contact">この連絡先と、この連絡先とのすべてのメッセージを削除してもよろしいですか?</string>
@@ -202,7 +140,6 @@
<string name="dialog_message_no_image_support">あなたのこの連絡先のBriarは画像の添付ファイルをまだサポートしていません。連絡先がアップグレードすると、別のアイコンが表示されます。</string>
<string name="dialog_title_image_support">この連絡先に画像を送信できるようになりました</string>
<string name="dialog_message_image_support">このアイコンをタップして画像を添付します。</string>
<string name="messaging_too_many_attachments_toast">最初の%d個の画像のみが送信されます。</string>
<!--Adding Contacts-->
<string name="add_contact_title">近くの人を連絡先に追加する</string>
<string name="face_to_face">連絡先として追加したい人と会う必要があります。\n\nこれにより、だれかがあなたになりすましたり、メッセージを読んだりするのを防ぐことができます。</string>
@@ -263,7 +200,6 @@
<string name="offline_state">インターネットに接続されていません</string>
<string name="duplicate_link_dialog_title">重複リンク</string>
<string name="duplicate_link_dialog_text_1">既に保留中の連絡先があります。リンク:%s</string>
<string name="duplicate_link_dialog_text_1_contact">既に連絡先があります。リンク:%s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%sと%sは同じ人ですか</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
@@ -294,7 +230,6 @@
<string name="introduction_response_accepted_sent">%1$sの紹介を受け入れました。</string>
<string name="introduction_response_accepted_sent_info">%1$sを連絡先に追加する前に、紹介を受け入れる必要があります。 これには時間がかかる場合があります。</string>
<string name="introduction_response_declined_sent">%1$sへの紹介を辞退しました。</string>
<string name="introduction_response_declined_auto">%1$sへの紹介は自動的に辞退されました。</string>
<string name="introduction_response_accepted_received">%1$sは%2$sの紹介を受け入れました。</string>
<string name="introduction_response_declined_received">%1$sは%2$sへの紹介を辞退しました。</string>
<string name="introduction_response_declined_received_by_introducee">%1$sによると、%2$sが紹介を辞退しました。</string>
@@ -339,7 +274,6 @@
</plurals>
<string name="groups_invitations_response_accepted_sent">%sからのグループ招待を受け入れました。</string>
<string name="groups_invitations_response_declined_sent">%sからのグループへの招待を辞退しました。</string>
<string name="groups_invitations_response_declined_auto">%sからのグループへの招待は自動的に辞退されました。</string>
<string name="groups_invitations_response_accepted_received">%sはグループへの招待を受け入れました。</string>
<string name="groups_invitations_response_declined_received">%sはグループへの招待を辞退しました。</string>
<string name="sharing_status_groups">グループに新しいメンバーを招待できるのは、作成者のみです。 以下は、グループの現在の全員のメンバーです。</string>
@@ -389,7 +323,6 @@
<string name="forum_invitation_already_sharing">既に共有しています</string>
<string name="forum_invitation_response_accepted_sent">%sからのフォーラムへの招待を受け入れました。</string>
<string name="forum_invitation_response_declined_sent">%sからのフォーラムへの招待を辞退しました。</string>
<string name="forum_invitation_response_declined_auto">%sからのフォーラムへの招待は自動的に辞退されました。</string>
<string name="forum_invitation_response_accepted_received">%sはフォーラムへの招待を受け入れました。</string>
<string name="forum_invitation_response_declined_received">%sはフォーラムへの招待を辞退しました。</string>
<string name="sharing_status">共有ステータス</string>
@@ -405,7 +338,7 @@
<string name="blogs_write_blog_post">ブログに投稿する</string>
<string name="blogs_write_blog_post_body_hint">ブログの投稿内容を入力してください</string>
<string name="blogs_publish_blog_post">公開</string>
<string name="blogs_blog_post_created">ブログ投稿が作成されました</string>
<string name="blogs_blog_post_created">ブログポストが作成されました</string>
<string name="blogs_blog_post_received">新しいブログの投稿を受け取りました</string>
<string name="blogs_blog_post_scroll_to">スクロール:</string>
<string name="blogs_feed_empty_state">表示する投稿がありません</string>
@@ -423,7 +356,6 @@
<string name="blogs_sharing_snackbar">選択した連絡先と共有するブログ</string>
<string name="blogs_sharing_response_accepted_sent">%sからのブログへの招待を受け入れました。</string>
<string name="blogs_sharing_response_declined_sent">%sからのブログへの招待を辞退しました。</string>
<string name="blogs_sharing_response_declined_auto">%sからのブログへの招待は自動的に辞退されました。</string>
<string name="blogs_sharing_response_accepted_received">%sはブログへの招待を受け入れました。</string>
<string name="blogs_sharing_response_declined_received">%sはブログへの招待を辞退しました。</string>
<string name="blogs_sharing_invitation_received">%1$sがブログ\"%2$s\"をあなたと共有しました。</string>
@@ -446,12 +378,8 @@
<string name="blogs_rss_feeds_manage_empty_state">表示するRSSフィードはありません\n\n「」アイコンをタップしてフィードをインポートします</string>
<string name="blogs_rss_feeds_manage_error">フィードの読み込み中に問題が発生しました。 後でもう一度やり直してください。</string>
<!--Settings Profile Picture-->
<string name="change_profile_picture">タップしてプロフィールの画像を変更</string>
<string name="dialog_confirm_profile_picture_title">プロフィールの画像を変更</string>
<string name="dialog_confirm_profile_picture_remark">連絡先のみがこの画像を閲覧可能</string>
<string name="change_profile_picture_failed_message">申し訳ありませんが、あなたのプロフィール画像の更新中に何か過ちが発生しました</string>
<!--Settings Display-->
<string name="pref_language_title">言語 &amp; </string>
<string name="pref_language_title">言語 &amp; </string>
<string name="pref_language_changed">この設定は、Briarを再起動すると有効になります。 サインアウトしてBriarを再起動してください。</string>
<string name="pref_language_default">システムのデフォルト</string>
<string name="display_settings_title">表示</string>
@@ -462,19 +390,10 @@
<string name="pref_theme_system">システムのデフォルト</string>
<!--Settings Connections-->
<string name="network_settings_title">接続</string>
<string name="bluetooth_setting">Bluetooth経由で連絡先に接続</string>
<string name="wifi_setting">同じWi-Fiネットワークで連絡先に接続</string>
<string name="tor_enable_title">インターネット経由で連絡先に接続</string>
<string name="tor_enable_summary">全接続をプライバシーのためにTorネットワークを通す</string>
<string name="tor_network_setting">Torネットワークの接続方法</string>
<string name="tor_network_setting_automatic">場所に基づいて自動的に接続する</string>
<string name="tor_network_setting_without_bridges">ブリッジなしでTorネットワークを使用する</string>
<string name="tor_network_setting_with_bridges">ブリッジを通してTorネットワークを使用する</string>
<string name="tor_network_setting_never">インターネットに接続しない</string>
<!--How and when Briar will connect to Tor: E.g. "Don't connect to the Internet (in China)" or "Use Tor network with bridges (in Belarus)"-->
<string name="tor_network_setting_summary">自動:%1$s%2$sのうち</string>
<string name="tor_mobile_data_title">モバイルデータを使用する</string>
<string name="tor_only_when_charging_title">充電時にのみインターネットに接続する</string>
<string name="tor_only_when_charging_summary">デバイスがバッテリー使用している場合、インターネット接続を無効にする</string>
<!--Settings Security and Panic-->
<string name="security_settings_title">セキュリティ</string>
@@ -528,7 +447,7 @@
<string name="notify_forum_posts_setting_title">フォーラム投稿</string>
<string name="notify_forum_posts_setting_summary">フォーラム投稿のアラートを表示する</string>
<string name="notify_forum_posts_setting_summary_26">フォーラム投稿のアラートを設定する</string>
<string name="notify_blog_posts_setting_title">ブログ投稿</string>
<string name="notify_blog_posts_setting_title">ブログポスト</string>
<string name="notify_blog_posts_setting_summary">ブログ投稿のアラートを表示する</string>
<string name="notify_blog_posts_setting_summary_26">ブログ投稿のアラートを設定する</string>
<string name="notify_vibration_setting">バイブレーション</string>
@@ -538,7 +457,6 @@
<string name="choose_ringtone_title">着信音を選択</string>
<string name="cannot_load_ringtone">着信音を読み込めません</string>
<!--Conversation Settings-->
<string name="disappearing_messages_title">消えたメッセージ</string>
<string name="learn_more">詳細情報</string>
<!--Settings Feedback-->
<string name="send_feedback">フィードバックを送信</string>
@@ -559,27 +477,18 @@
<string name="optional_contact_email">あなたのメールアドレス(任意)</string>
<string name="include_debug_report_crash">クラッシュに関する匿名のデータを添付する</string>
<string name="include_debug_report_feedback">このデバイスに関する匿名のデータを添付する</string>
<string name="dev_report_user_info">ユーザー情報</string>
<string name="dev_report_basic_info">基本情報</string>
<string name="dev_report_device_info">デバイス情報</string>
<string name="dev_report_stacktrace">スタックトレース</string>
<string name="dev_report_time_info">時間情報</string>
<string name="dev_report_memory">メモリー</string>
<string name="dev_report_storage">ストレージ</string>
<string name="dev_report_connectivity">接続</string>
<string name="dev_report_logcat">アプリのログ</string>
<string name="dev_report_device_features">デバイスの機能</string>
<string name="send_report">レポートを送信</string>
<string name="close">閉じる</string>
<string name="dev_report_sending">フィードバックを送信中…</string>
<string name="dev_report_sent">フィードバックを送信しました</string>
<string name="dev_report_saved">レポートを保存しました。 次回、Briarにログインしたときに送信されます。</string>
<!--Sign Out-->
<string name="progress_title_logout">Briarからサインアウト中…</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">スクリーンオーバーレイが検出されました</string>
<string name="screen_filter_body">別のアプリがBriarの画面上に描画しています。 セキュリティを保護するために、Briarは、別のアプリがBriarの画面上に描画している場合、タッチに応答しません。\n\n次のアプリが上に描画されている可能性があります\n\n%1$s</string>
<string name="screen_filter_body_api_30">他のアプリがBriarの上に描画されています。セキュリティ保護のため、他のアプリが上に描画しているときは、Briarに触れても反応しません。\n\n以下のアプリを確認して、原因のアプリを見つけてください。</string>
<string name="screen_filter_allow">これらのアプリがBriarの画面上に描画できるようにする</string>
<!--Permission Requests-->
<string name="permission_camera_title">カメラへのアクセス許可</string>
@@ -599,7 +508,6 @@
<string name="lock_is_locked">Briarはロックされています</string>
<string name="lock_tap_to_unlock">タップしてロック解除</string>
<!--Connections Screen-->
<string name="transports_help_text">Briarは、インターネット、Wi-Fi、Bluetoothを介して連絡先に接続することができます。\n\nすべてのインターネット接続は、プライバシー保護のためにTorネットワークを経由します。\n\n複数の方法で連絡が取れる場合、Briarはそれらを並行して使用します。</string>
<!--Screenshots-->
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_alice">アリス</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">밑의 버튼을 눌러 Briar를 \"보호된 앱\" 스크린에서 보호되게 해 주세요.</string>
<string name="setup_huawei_button">Briar 보호하기</string>
<string name="setup_huawei_help">Briar를 보호된 앱 목록에 추가하지 않는다면, 백그라운드에서 실행될 수 없습니다.</string>
<string name="setup_xiaomi_button">Briar 보호하기</string>
<string name="warning_dozed">%s가 백그라운드에서 실행될 수 없었습니다</string>
<!--Login-->
<string name="enter_password">비밀번호</string>

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">Bakstelėkite mygtuką žemiau, atverkite langą „Programų paleidimas (angl. App launch)“ ir įsitikinkite, kad Briar yra nustatyta į „Tvarkyti rankiniu būdu (angl. Manage manually)“.</string>
<string name="setup_huawei_app_launch_button">Atverti akumuliatoriaus nustatymus</string>
<string name="setup_huawei_app_launch_help">Jeigu „Programų paleidimo (angl. App launch)“ lange Briar nėra nustatyta į „Tvarkyti rankiniu būdu (angl. Manage manually)“, tuomet programėlė negalės veikti fone.</string>
<string name="setup_xiaomi_text">Tam, kad galėtų veikti fone, Briar turi būti prirakinta prie paskiausiųjų programėlių sąrašo.</string>
<string name="setup_xiaomi_button">Apsaugoti Briar</string>
<string name="setup_xiaomi_help">Jei Briar nebus prirakinta prie paskiausiųjų programėlių sąrašo, ji negalės veikti fone.</string>
<string name="setup_xiaomi_dialog_body_old">1. Atverkite paskiausiųjų programėlių sąrašą (dar vadinamą programėlių perjungikliu)\n\n2. Ant Briar paveiksliuko perbraukite žemyn, kad būtų rodoma pakabinamos spynos piktograma\n\n3. Jei pakabinama spyna neužrakinta, bakstelėkite, kad ją užrakintumėte</string>
<string name="setup_xiaomi_dialog_body_new">1. 1. Atverkite paskiausiųjų programėlių sąrašą (dar vadinamą programėlių perjungikliu)\n\n2. Paspauskite ir laikykite ant Briar paveiksliuko tol, kol atsiras pakabinamos spynos mygtukas\n\n3. Jei pakabinama spyna neužrakinta, bakstelėkite, kad ją užrakintumėte</string>
<string name="warning_dozed">%s nepavyko pasileisti fone</string>
<!--Login-->
<string name="enter_password">Slaptažodis</string>
@@ -489,8 +484,6 @@
<string name="blogs_rss_feeds_import_button">Importuoti</string>
<string name="blogs_rss_feeds_import_hint">Įveskite RSS kanalo URL</string>
<string name="blogs_rss_feeds_import_error">Atleiskite! Importuojant jūsų kanalą, įvyko klaida.</string>
<string name="blogs_rss_feeds_import_exists">Tas kanalas jau yra importuotas.</string>
<string name="blogs_rss_feeds">RSS kanalai</string>
<string name="blogs_rss_feeds_manage_imported">Importuota:</string>
<string name="blogs_rss_feeds_manage_author">Autorius:</string>
<string name="blogs_rss_feeds_manage_updated">Paskutinį kartą atnaujinta:</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Допрете го копчето подолу и осигурајте се дека Briar е заштитен во \"Protected Apps\" / мак. \"Заштитени апликации\" екранот.</string>
<string name="setup_huawei_button">Заштити го Briar</string>
<string name="setup_huawei_help">Ако Briar не е додаден на листата со заштитени апликации, ќе биде оневозможен да работи во позадина.</string>
<string name="setup_xiaomi_button">Заштити го Briar</string>
<string name="warning_dozed">%s беше оневозможен да работи во позадина</string>
<!--Login-->
<string name="enter_password">Лозинка</string>

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">အောက်က ခလုတ်ကို နှိပ်၍ \"အပ္ပလီကေးရှင်း ဖွင့်ရန်\" စကင်ရင်ကို ဖွင့်ပါ၊ ပြီးလျှင် Briar ကို \"ကိုယ်တိုင်စီမံမယ်\" လို့ သတ်မှတ်ပါ။</string>
<string name="setup_huawei_app_launch_button">ဘက်ထရီအပြင်အဆင်များကို ဖွင့်ပါ</string>
<string name="setup_huawei_app_launch_help">Briar ကို \"အပ္ပလီကေးရှင်း ဖွင့်ရန်\" စကင်ရင်ပေါ်တွင် \"ကိုယ်တိုင်စီမံမယ်\" လို့မသတ်မှတ်ထားလျှင် နောက်ခံတွင် ၎င်းကို လည်ပတ်၍ မရနိုင်ပါ။</string>
<string name="setup_xiaomi_text">နောက်ခံတွင်အလုပ်လုပ်စေရန် လတ်တလောအက်ပလီကေးရှင်းများစာရင်းတွင် Briar အား သော့ခတ်ထားရန် လိုအပ်သည်။</string>
<string name="setup_xiaomi_button">Briar ကို ကာကွယ်ပါ</string>
<string name="setup_xiaomi_help">အကယ်၍ Briar ကို သော့မခတ်ထားပါက၊ ထိုအက်ပလီကေးရှင်းသည် နောက်ခံတွင် အလုပ်လုပ်နေနိုင်မည် မဟုတ်ပါ။</string>
<string name="setup_xiaomi_dialog_body_old">၁။ လတ်တလောအက်ပ်များစာရင်းကိုဖွင့်ပါ (ဒါ့အပြင် app switcher လို့လည်းခေါ်ပါသည်) \n\n2. သော့ခလောက်အိုင်ကွန်ကို \n\n3. ပြသရန် Briar ၏ပုံကို အောက်ကိုတွန်းပါ။ </string>
<string name="setup_xiaomi_dialog_body_new">၁။ လတ်တလော အက်ပလီကေးရှင်းများစာရင်း (ဒါ့အပြင် အက်ပလီကေးရှင်းခလုတ်ဟုလည်းခေါ်သည်) ကိုလည်းဖွင့်ပါ \n\n2 ။ အဆိုပါသော့ခလောက်ခလုတ်မှ \n\n3 ပေါ်လာသည်အထိ Briar ၏ ပုံကို နှိပ်၍ ဖိထားပါ။ အကယ်၍ သော့ခလောက်သည် သော့ခတ်ထားခြင်းမရှိပါက ၎င်းကိုသော့ခတ်ရန် ပွတ်ဆွဲပါ။ </string>
<string name="warning_dozed">%s ကို နောက်ကွယ်တွင် မဖွင့်ထားနိုင်ပါ</string>
<!--Login-->
<string name="enter_password">စကားဝှက်</string>
@@ -365,7 +360,7 @@
<string name="groups_reveal_invisible">အဆက်အသွယ်နှင့် ဆက်သွယ်မှုကို အဖွဲ့မှ မတွေ့မြင်နိုင်ပါ</string>
<!--Forums-->
<string name="no_forums">ပြသစရာဖိုရမ် မရှိပါ</string>
<string name="no_forums_action">+ (အပေါင်းအိုင်ကွန်) အား နှိပ်၍ ဆွေးနွေးမှုဖိုရမ် ဖန်တီးပါ သို့မဟုတ် သင့် အဆက်အသွယ်များကို ဆွေးနွေးမှုဖိုရမ်များကို သင့်ီသို့ ဝေမျှရန် တောင်းဆိုပါ </string>
<string name="no_forums_action">+ (အပေါင်းအိုင်ကွန်) အား နှိပ်၍ ဆွေးနွေးမှုဖိုရမ် ဖန်တီးပါ သို့မဟုတ် သင့် အဆက်အသွယ်များကို ဆွေးနွေးမှုဖိုရမ်များကို သင့်ီသို့ ဝေမျှရန် တောင်းဆိုပါ</string>
<string name="create_forum_title">ဖိုရမ် ဖန်တီးမယ်</string>
<string name="choose_forum_hint">သင့် ဆွေးနွေးမှုဖိုရမ်အတွက် အမည်ရွေးပါ</string>
<string name="create_forum_button">ဖိုရမ်ဖန်တီးရန်</string>
@@ -450,14 +445,14 @@
<string name="blogs_rss_feeds_import_button">တင်သွင်းမယ်</string>
<string name="blogs_rss_feeds_import_hint">RSS သတင်းပို့စ်အလွှာ၏ URL ရိုက်ထည့်ပေးပါ</string>
<string name="blogs_rss_feeds_import_error">ဝမ်းနည်းပါသည်! သင့် သတင်းပို့စ်အလွှာအား တင်သွင်းရာတွင် ပျက်ကွက်မှု ဖြစ်ခဲ့ပါသည်။</string>
<string name="blogs_rss_feeds_import_exists">Feed ကို တင်ပို့ပြီး ဖြစ်သည်။</string>
<string name="blogs_rss_feeds">RSS သတင်းလွှာများ</string>
<string name="blogs_rss_feeds_manage">RSS သတင်ပို့စ်အလွှာများကို စီမံမယ်</string>
<string name="blogs_rss_feeds_manage_imported">တင်သွင်းထားသော</string>
<string name="blogs_rss_feeds_manage_author">စာရေးဆရာ -</string>
<string name="blogs_rss_feeds_manage_updated">နောက်ဆုံးအပ်ဒိတ်လုပ်ခဲ့ခြင်း -</string>
<string name="blogs_rss_remove_feed">သတင်းပို့စ်အလွှာ ဖယ်ရှားမယ်</string>
<string name="blogs_rss_remove_feed_dialog_message">ဤသတင်းပို့စ်အလွှာကို ဖယ်ရှားချင်တာ သေချာပါသလား? \n\n ပို့စ်များကို သင့်ကိရိယာမှ ဖယ်ရှားလိုက်မည်ဖြစ်သော်လည်း အခြားသူများ၏ကိရိယာများမှမူ ဖယ်ရှားလိုက်မည်မဟုတ်ပါ။ \n\n သင်မှ ဤသတင်းပို့စ်အလွှာကို မျှဝေထားသည့် သင့်အဆက်အသွယ်များအနေဖြင့် သတင်းအသစ်များရရှိမှု ရပ်တန့်သွားနိုင်ပါသည်။</string>
<string name="blogs_rss_remove_feed_ok">ဖယ်ရှားမယ်</string>
<string name="blogs_rss_feeds_manage_delete_error">သတင်းပို့စ်အလွှာကို ဖယ်ရှား၍မရပါ!</string>
<string name="blogs_rss_feeds_manage_empty_state">RSS သတင်းပို့စ်အလွှာများပြရန် မရှိပါ \n\n + (အပေါင်းအိုင်ကွန်) အားနှိပ်၍ သတင်းအလွှာ တင်သွင်းပါ</string>
<string name="blogs_rss_feeds_manage_error">သင့် သတင်းပို့စ်အလွှာကို တင်သွင်းရာတွင် အခက်အခဲတွေ့ကြုံခဲ့ပါသည်။ နောင်မှ ပြန်စမ်းကြည့်ပေးပါ။</string>
<!--Settings Profile Picture-->

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Tik alsjeblieft op de knop hieronder en ga nadat Briar is beschermd naar het scherm \"Beveiligde apps\".</string>
<string name="setup_huawei_button">Bescherm Briar</string>
<string name="setup_huawei_help">Als Briar niet is toegevoegd aan de beschermde apps, is het niet mogelijk om het in de achtergrond uit te voeren.</string>
<string name="setup_xiaomi_button">Bescherm Briar</string>
<string name="warning_dozed">%s kon niet in de achtergrond worden uitgevoerd</string>
<!--Login-->
<string name="enter_password">Wachtwoord</string>
@@ -421,7 +420,6 @@
<string name="blogs_rss_feeds_import_button">Importeer</string>
<string name="blogs_rss_feeds_import_hint">Voer de URL van de RSS-feed in</string>
<string name="blogs_rss_feeds_import_error">Excuses! Er trad een fout op bij het importeren van je feed.</string>
<string name="blogs_rss_feeds">RSS-feeds</string>
<string name="blogs_rss_feeds_manage_imported">Geïmporteerd:</string>
<string name="blogs_rss_feeds_manage_author">Auteur:</string>
<string name="blogs_rss_feeds_manage_updated">Laatst bijgewerkt:</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Mercé de tocar lo boton çai-jos per vos assegurar que Briar es protegit dins lecran « Aplicacions protegidas ».</string>
<string name="setup_huawei_button">Projècte Briar</string>
<string name="setup_huawei_help">Se Briar es pas ajustat a la lista de las aplicacion protegidas poirà pas sexecutar en rèire plan.</string>
<string name="setup_xiaomi_button">Projècte Briar</string>
<string name="warning_dozed">%s a pas pogut sexecutar en rèire plan</string>
<!--Login-->
<string name="enter_password">Senhal</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Proszę dotknąć przycisku poniżej i upewnić się, że Briar jest na liście chronionych aplikacji.</string>
<string name="setup_huawei_button">Chroń Briar</string>
<string name="setup_huawei_help">Jeśli Briar nie będzie na liście chronionych aplikacji, nie będzie miał możliwości aby działać w tle.</string>
<string name="setup_xiaomi_button">Chroń Briar</string>
<string name="warning_dozed">%s nie był wstanie działać w tle</string>
<!--Login-->
<string name="enter_password">Hasło</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Toque no botão abaixo e verifique se o Briar está protegido na tela \"Aplicativos Protegidos\"</string>
<string name="setup_huawei_button">Proteger o Briar</string>
<string name="setup_huawei_help">Se o Briar não for adicionado à lista de aplicativos protegidos ele não poderá ser executado em segundo plano.</string>
<string name="setup_xiaomi_button">Proteger o Briar</string>
<string name="warning_dozed">%s não pôde ser executado em segundo plano</string>
<!--Login-->
<string name="enter_password">Senha</string>
@@ -421,7 +420,6 @@
<string name="blogs_rss_feeds_import_button">Importar</string>
<string name="blogs_rss_feeds_import_hint">Entre a URL do feed RSS</string>
<string name="blogs_rss_feeds_import_error">Nós lamentamos! Houve um erro ao importar seu Feed.</string>
<string name="blogs_rss_feeds">Feeds RSS</string>
<string name="blogs_rss_feeds_manage_imported">Importado:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Última Atualização:</string>

View File

@@ -22,8 +22,6 @@
<string name="setup_huawei_text">Vă rugăm să apăsați butonul de mai jos și să vă asigurați că Briar este marcat ca protejat în fereastra de \"Aplicații protejate\".</string>
<string name="setup_huawei_button">Protejează Briar</string>
<string name="setup_huawei_help">Dacă Briar nu este adăugat în lista de aplicații protejate, nu va fi capabil să ruleze în fundal.</string>
<string name="setup_huawei_app_launch_button">Deschide setările de baterie</string>
<string name="setup_xiaomi_button">Protejează Briar</string>
<string name="warning_dozed">%s nu poate rula în fundal</string>
<!--Login-->
<string name="enter_password">Parola</string>
@@ -160,7 +158,6 @@
<string name="image_attach_error_invalid_mime_type">Format de imagine incompatibil: %s</string>
<string name="set_contact_alias">Schimbă nume contact</string>
<string name="set_contact_alias_hint">Nume contact</string>
<string name="menu_item_disappearing_messages">Mesaje ce dispar</string>
<string name="menu_item_connect_via_bluetooth">Conectare prin Bluetooth</string>
<string name="dialog_title_connect_via_bluetooth">Conectare prin Bluetooth</string>
<!--The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more."-->
@@ -182,8 +179,6 @@
<item quantity="other">%d de zile</item>
</plurals>
<!--The first placeholder will show a contact's name. The second placeholder at the end will add "Tap to learn more."-->
<string name="tap_to_learn_more">Atingeți pentru mai mult.</string>
<string name="auto_delete_changed_warning_send">Trimite oricum</string>
<string name="delete_all_messages">Șterge toate mesajele</string>
<string name="dialog_title_delete_all_messages">Confirmare ștergere mesaj</string>
<string name="dialog_message_delete_all_messages">Sigur doriți să ștergeți toate mesajele?</string>
@@ -450,8 +445,6 @@
<string name="blogs_rss_feeds_import_button">Importă</string>
<string name="blogs_rss_feeds_import_hint">Introduceți URL-ul fluxului RSS</string>
<string name="blogs_rss_feeds_import_error">Ne pare rău! A apărut o eroare la importul fluxului dumneavoastră.</string>
<string name="blogs_rss_feeds_import_exists">Acest flux este deja importat.</string>
<string name="blogs_rss_feeds">Fluxuri RSS</string>
<string name="blogs_rss_feeds_manage_imported">Importat:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Actualizat ultima dată:</string>
@@ -553,7 +546,6 @@
<string name="choose_ringtone_title">Alegeți sunetul</string>
<string name="cannot_load_ringtone">Nu se poate încărca sunetul</string>
<!--Conversation Settings-->
<string name="disappearing_messages_title">Mesaje ce dispar</string>
<string name="learn_more">Află mai mult</string>
<!--Settings Feedback-->
<string name="send_feedback">Trimiteți feed-back</string>

View File

@@ -22,14 +22,6 @@
<string name="setup_huawei_text">Нажмите кнопку ниже и убедитесь, что Briar отображается на экране защищенных приложений.</string>
<string name="setup_huawei_button">Защитить Briar</string>
<string name="setup_huawei_help">Если Briar не будет добавлен в список защищенных приложений, он не сможет работать в фоновом режиме.</string>
<string name="setup_huawei_app_launch_text">Пожалуйста, нажмите кнопку ниже, откройте экран \"Запуск приложения\" и убедитесь, что для Briar установлено значение \"Управлять вручную\".</string>
<string name="setup_huawei_app_launch_button">Открыть настройки батареи</string>
<string name="setup_huawei_app_launch_help">Если для Briar не установлено значение \"Управлять вручную\" на экране \"Запуск приложения\", он не сможет работать в фоновом режиме.</string>
<string name="setup_xiaomi_text">Для работы в фоновом режиме Briar должен быть заблокирован в списке недавних приложений.</string>
<string name="setup_xiaomi_button">Защитить Briar</string>
<string name="setup_xiaomi_help">Если Briar не заблокирован в списке последних приложений, он не сможет работать в фоновом режиме.</string>
<string name="setup_xiaomi_dialog_body_old">1. Откройте список недавних приложений (также называемый переключателем приложений)\n\n2. Смахните вниз изображение Briar, чтобы появился значок замка\n\n3. Если замок не заблокирован, нажмите на него для блокировки.</string>
<string name="setup_xiaomi_dialog_body_new">1. Откройте список недавних приложений (также называемый переключателем приложений)\n\n2. Нажмите и удерживайте изображение Briar, пока не появится кнопка с замком\n\n3. Если замок не заблокирован, нажмите на него для блокировки.</string>
<string name="warning_dozed">%s не удалось выполнить в фоновом режиме</string>
<!--Login-->
<string name="enter_password">Пароль</string>
@@ -178,13 +170,6 @@
<string name="menu_item_disappearing_messages">Исчезающие сообщения</string>
<string name="menu_item_connect_via_bluetooth">Подключение через Bluetooth</string>
<string name="dialog_title_connect_via_bluetooth">Подключение через Bluetooth</string>
<string name="dialog_message_connect_via_bluetooth">Чтобы это сработало, ваш контакт должен находиться поблизости.\n\nВы и ваш собеседник должны одновременно нажать кнопку \"Начать\".</string>
<string name="toast_connect_via_bluetooth_already_discovering">Пробуем подключиться через Bluetooth</string>
<string name="toast_connect_via_bluetooth_not_discoverable">Невозможно продолжить без Bluetooth</string>
<string name="toast_connect_via_bluetooth_no_location_permission">Невозможно продолжить без доступа к местоположению</string>
<string name="toast_connect_via_bluetooth_start">Подключение через Bluetooth...</string>
<string name="toast_connect_via_bluetooth_success">Успешное подключение через Bluetooth</string>
<string name="toast_connect_via_bluetooth_error">Не удалось подключиться через Bluetooth</string>
<!--The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more."-->
<string name="auto_delete_msg_you_enabled">Ваши сообщения исчезнут спустя %1$s. %2$s</string>
<!--The placeholder at the end will add "Tap to learn more."-->
@@ -491,8 +476,6 @@
<string name="blogs_rss_feeds_import_button">Импорт</string>
<string name="blogs_rss_feeds_import_hint">Введите URL-адрес RSS-ленты</string>
<string name="blogs_rss_feeds_import_error">Мы сожалеем! Произошла ошибка при импорте ленты.</string>
<string name="blogs_rss_feeds_import_exists">Эта лента уже импортирована.</string>
<string name="blogs_rss_feeds">RSS-ленты</string>
<string name="blogs_rss_feeds_manage_imported">Импортирован:</string>
<string name="blogs_rss_feeds_manage_author">Автор:</string>
<string name="blogs_rss_feeds_manage_updated">Последнее обновление:</string>

View File

@@ -25,12 +25,6 @@
<string name="setup_huawei_app_launch_text">Ju lutemi, prekni butonin më poshtë, hapni skenën “Nisje aplikacioni” dhe sigurohuni se për Briar-in është zgjedhur “Administrojeni dorazi”.</string>
<string name="setup_huawei_app_launch_button">Hapni Rregullime Baterie</string>
<string name="setup_huawei_app_launch_help">Nëse për Briar-in sështë zgjedhur “Administroje Dorazi” te skena “Nisje Aplikacioni”, sdo të jetë në gjendje të xhirojë në prapaskenë.</string>
<string name="setup_xiaomi_text">Për të xhiruar në prapaskenë, Briar-i lyp të jetë i kyçur te lista e aplikacioneve të përdorur së fundi.</string>
<string name="setup_xiaomi_button">Mbroje Briar-in</string>
<string name="setup_xiaomi_help">Nëse Briar-i sështë i kyçur te lista e aplikacioneve të përdorur së fundi, sdo të jetë në gjendje të xhirojë në prapaskenë.</string>
<string name="setup_xiaomi_dialog_body_old">1. Hapni listën e aplikacioneve përdorur së fundi (e quajtur edhe këmbyes aplikacionesh)\n\n2. Fërkojeni për poshtë te figura për Briar-in, që të shfaqet ikona e drynit\n\n3. Nëse dryni sështë o kyçur, prekeni që të kyçet</string>
<string name="setup_xiaomi_dialog_body_new">1. Hapni listën e aplikacioneve përdorur së fundi (e quajtur edhe
këmbyes aplikacionesh)\n\n2. Shtypni dhe mbajeni të shtypur figurën e Briar-it, deri sa të shfaqet butoni i drynit\n\n3. Nëse dryni sështë o kyçur, prekeni që të kyçet</string>
<string name="warning_dozed">%s s\qe në gjendje të xhirojë në prapaskenë</string>
<!--Login-->
<string name="enter_password">Fjalëkalim</string>
@@ -464,8 +458,6 @@ këmbyes aplikacionesh)\n\n2. Shtypni dhe mbajeni të shtypur figurën e Briar-i
<string name="blogs_rss_feeds_import_button">Importo</string>
<string name="blogs_rss_feeds_import_hint">Jepni URL-në e prurjes RSS</string>
<string name="blogs_rss_feeds_import_error">Na ndjeni! Pati një gabim me importimin e prurjes tuaj.</string>
<string name="blogs_rss_feeds_import_exists">Ajo prurje është importuar tashmë.</string>
<string name="blogs_rss_feeds">Prurje RSS</string>
<string name="blogs_rss_feeds_manage_imported">Të importuara:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Përditësuar Së Fundi:</string>

View File

@@ -22,7 +22,6 @@
<string name="setup_huawei_text">Tryck på knappen längre ner för att lägga till Briar till listan över \"skyddade appar\".</string>
<string name="setup_huawei_button">Skydda Briar</string>
<string name="setup_huawei_help">Om Briar inte läggs till listan över skyddade appar kan det inte köras i bakgrunden.</string>
<string name="setup_xiaomi_button">Skydda Briar</string>
<string name="warning_dozed">%s kunde inte köras i bakgrunden</string>
<!--Login-->
<string name="enter_password">Lösenord</string>
@@ -421,7 +420,6 @@
<string name="blogs_rss_feeds_import_button">Importera</string>
<string name="blogs_rss_feeds_import_hint">Skriv URL till RSS-flödet</string>
<string name="blogs_rss_feeds_import_error">Tyvärr! Något gick fel när flödet skulle importeras.</string>
<string name="blogs_rss_feeds">RSS-flöden</string>
<string name="blogs_rss_feeds_manage_imported">Importerade:</string>
<string name="blogs_rss_feeds_manage_author">Författare:</string>
<string name="blogs_rss_feeds_manage_updated">Senast uppdaterad:</string>

View File

@@ -26,7 +26,6 @@ Nenosiri zako haziendani </string>
<string name="setup_huawei_button">Linda Briar </string>
<string name="setup_huawei_help">
Kama Briar haija wekwa kwenye orotha programu ulizi, haitaweza kufanya kazi kwenye nyuma ya mkongo</string>
<string name="setup_xiaomi_button">Linda Briar </string>
<string name="warning_dozed">%shaijawezekana kujiendesha kwa nyuma ya mkongo</string>
<!--Login-->
<string name="enter_password">Nywila

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">Lütfen aşağıdaki düğmeye dokunun, \"Uygulama başlatma\" ekranınıın ve Briar için \"Manuel olarak yönet\" şeklinde ayarlanmış olduğundan emin olun.</string>
<string name="setup_huawei_app_launch_button">Pil Ayarlarını</string>
<string name="setup_huawei_app_launch_help">Eğer Briar, \"Uygulama Başlatma\" ekranında \"Manuel olarak yönet\" olarak ayarlanmamışsa, arka planda çalışmayabilir.</string>
<string name="setup_xiaomi_text">Briar\'ın arka planda çalışması için korunan uygulamalar listesine listesine eklenmesi gerekiyor.</string>
<string name="setup_xiaomi_button">Briar\'ı Koru</string>
<string name="setup_xiaomi_help">Briar korunan uygulamalar listesine eklenmezse, arka planda çalışamaz.</string>
<string name="setup_xiaomi_dialog_body_old">1. Geçmiş uygulamalar listesini açın (uygulama değiştirici de deniyor)\n\n2. Briar resmini kilit simgesini görene kadar aşağı kaydırın\n\n3. Eğer kilit simgesi seçili değilse, kilitlemek için dokunun</string>
<string name="setup_xiaomi_dialog_body_new">1. Geçmiş uygulamalar listesini açın (uygulama değiştirici de deniyor)\n\n2. Kilit simgesini görene kadar Briar resmine tıklayın ve tutun\n\n3. Eğer kilit simgesi seçili değilse, kilitlemek için dokunun</string>
<string name="warning_dozed">%s arka planda çalışamadı</string>
<!--Login-->
<string name="enter_password">Parola</string>
@@ -463,8 +458,6 @@
<string name="blogs_rss_feeds_import_button">İçe Aktar</string>
<string name="blogs_rss_feeds_import_hint">RSS beslemesi URL\'sini girin</string>
<string name="blogs_rss_feeds_import_error">Üzgünüz! RSS beslemeniz içe aktarılırken bir hata oluştu.</string>
<string name="blogs_rss_feeds_import_exists">Bu besleme zaten içe aktarılmış.</string>
<string name="blogs_rss_feeds">RSS beslemeleri</string>
<string name="blogs_rss_feeds_manage_imported">İçe Aktarıldı:</string>
<string name="blogs_rss_feeds_manage_author">Yazar:</string>
<string name="blogs_rss_feeds_manage_updated">Son Güncelleme:</string>

View File

@@ -25,11 +25,6 @@
<string name="setup_huawei_app_launch_text">轻按下方按钮打开“App launch”界面确保 Briar 被设置为“手动管理”。</string>
<string name="setup_huawei_app_launch_button">打开电池设置</string>
<string name="setup_huawei_app_launch_help">如果“App 启动”界面中 Briar 没有被设置为“手动管理”,则其将无法在后台运行。</string>
<string name="setup_xiaomi_text">要在后台运行,需将 Briar 锁定在最近的应用列表中。</string>
<string name="setup_xiaomi_button">保护 Briar</string>
<string name="setup_xiaomi_help">如果未将 Briar 锁定到最近的应用列表,它将无法在后台运行。</string>
<string name="setup_xiaomi_dialog_body_old">1. 打开最近的应用列表(也称为应用切换器)\n\n2. 向下滑动Briar的图像显示挂锁图标\n\n3.如果挂锁没有被锁上,轻敲以锁定它</string>
<string name="setup_xiaomi_dialog_body_new">1. 打开最近的应用列表(也称为应用切换器)\n\n2. 按住Briar的图像直到挂锁按钮出现\n\n3. 如果挂锁没有被锁上,轻敲以锁定它</string>
<string name="warning_dozed">%s 无法在后台运行</string>
<!--Login-->
<string name="enter_password">密码</string>
@@ -450,8 +445,6 @@
<string name="blogs_rss_feeds_import_button">导入</string>
<string name="blogs_rss_feeds_import_hint">输入 RSS 订阅源链接</string>
<string name="blogs_rss_feeds_import_error">抱歉!导入订阅源时发生错误。</string>
<string name="blogs_rss_feeds_import_exists">已经导入那个源</string>
<string name="blogs_rss_feeds">RSS源 </string>
<string name="blogs_rss_feeds_manage_imported">已导入:</string>
<string name="blogs_rss_feeds_manage_author">作者:</string>
<string name="blogs_rss_feeds_manage_updated">最后更新于:</string>

View File

@@ -30,11 +30,6 @@
<string name="setup_huawei_app_launch_text">Please tap the button below, open the \"App launch\" screen and make sure Briar is set to \"Manage manually\".</string>
<string name="setup_huawei_app_launch_button">Open Battery Settings</string>
<string name="setup_huawei_app_launch_help">If Briar is not set to \"Manage manually\" in the \"App launch\" screen, it will not be able to run in the background.</string>
<string name="setup_xiaomi_text">To run in the background, Briar needs to be locked to the recent apps list.</string>
<string name="setup_xiaomi_button">Protect Briar</string>
<string name="setup_xiaomi_help">If Briar is not locked to the recent apps list, it will be unable to run in the background.</string>
<string name="setup_xiaomi_dialog_body_old">1. Open the recent apps list (also called the app switcher)\n\n2. Swipe down on the image of Briar to show the padlock icon\n\n3. If the padlock is not locked, tap to lock it</string>
<string name="setup_xiaomi_dialog_body_new">1. Open the recent apps list (also called the app switcher)\n\n2. Press and hold the image of Briar until the padlock button appears\n\n3. If the padlock is not locked, tap to lock it</string>
<string name="warning_dozed">%s was unable to run in the background</string>
<!-- Login -->

View File

@@ -74,6 +74,7 @@ internal class HeadlessModule(private val appDir: File) {
}
@Provides
@Singleton
internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig {
val duplex: List<DuplexPluginFactory> =
if (isLinux() || isMac()) listOf(tor) else emptyList()

View File

@@ -64,6 +64,7 @@ internal class HeadlessTestModule(private val appDir: File) {
}
@Provides
@Singleton
internal fun providePluginConfig(): PluginConfig {
return object : PluginConfig {
override fun getDuplexFactories(): Collection<DuplexPluginFactory> = emptyList()

View File

@@ -136,7 +136,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
val messageId1 = MessageId(getRandomId())
val messageId2 = MessageId(getRandomId())
val messageIds = listOf(messageId1, messageId2)
val event = MessagesSentEvent(contact.id, messageIds)
val event = MessagesSentEvent(contact.id, messageIds, 1234)
every {
webSocketController.sendEvent(
@@ -274,7 +274,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
val messageId1 = MessageId(getRandomId())
val messageId2 = MessageId(getRandomId())
val messageIds = listOf(messageId1, messageId2)
val event = MessagesSentEvent(contact.id, messageIds)
val event = MessagesSentEvent(contact.id, messageIds, 1234)
val json = """
{