mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Compare commits
24 Commits
release-1.
...
Alarms
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb7d4076ab | ||
|
|
a3083a6e73 | ||
|
|
c1fcae7de3 | ||
|
|
35c1f50650 | ||
|
|
f2c50b500e | ||
|
|
0776ab85b6 | ||
|
|
f77bde4edf | ||
|
|
afa9b6193a | ||
|
|
1bcedea34a | ||
|
|
0f16ac57f3 | ||
|
|
7ecac1867e | ||
|
|
331c09a02a | ||
|
|
7e05a49bda | ||
|
|
eac1f9ed74 | ||
|
|
d16aa9e2a4 | ||
|
|
cc72d146a0 | ||
|
|
bff23480d7 | ||
|
|
e435578f3b | ||
|
|
33b9539a72 | ||
|
|
a114d4db15 | ||
|
|
73b7879c64 | ||
|
|
e622a518ac | ||
|
|
28ea3d014a | ||
|
|
8c64734ff1 |
@@ -21,11 +21,7 @@ test:
|
|||||||
|
|
||||||
|
|
||||||
test_reproducible:
|
test_reproducible:
|
||||||
image: briar/reproducer:latest
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cd /opt/briar-reproducer
|
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
|
||||||
- ./reproduce.py ${CI_COMMIT_REF_NAME}
|
|
||||||
|
|
||||||
only:
|
only:
|
||||||
- tags
|
- tags
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 10010
|
versionCode 10011
|
||||||
versionName "1.0.10"
|
versionName "1.0.11"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -31,6 +33,12 @@ dependencies {
|
|||||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
||||||
|
|
||||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||||
|
|
||||||
|
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||||
|
androidTestImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
||||||
|
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyVerification {
|
dependencyVerification {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.event.EventModule;
|
||||||
|
import org.briarproject.bramble.plugin.PluginModule;
|
||||||
|
import org.briarproject.bramble.plugin.tor.BridgeTest;
|
||||||
|
import org.briarproject.bramble.system.SystemModule;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = {
|
||||||
|
BrambleAndroidModule.class,
|
||||||
|
PluginModule.class, // needed for BackoffFactory
|
||||||
|
EventModule.class,
|
||||||
|
SystemModule.class,
|
||||||
|
})
|
||||||
|
public interface IntegrationTestComponent {
|
||||||
|
|
||||||
|
void inject(BridgeTest init);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.DaggerIntegrationTestComponent;
|
||||||
|
import org.briarproject.bramble.IntegrationTestComponent;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class BridgeTest extends BrambleTestCase {
|
||||||
|
|
||||||
|
private final static long TIMEOUT = SECONDS.toMillis(23);
|
||||||
|
|
||||||
|
private final static Logger LOG =
|
||||||
|
Logger.getLogger(BridgeTest.class.getSimpleName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EventBus eventBus;
|
||||||
|
@Inject
|
||||||
|
BackoffFactory backoffFactory;
|
||||||
|
@Inject
|
||||||
|
Clock clock;
|
||||||
|
|
||||||
|
private final Context appContext = getTargetContext();
|
||||||
|
private final CircumventionProvider circumventionProvider;
|
||||||
|
private final List<String> bridges;
|
||||||
|
private TorPluginFactory factory;
|
||||||
|
private volatile int currentBridge = 0;
|
||||||
|
|
||||||
|
public BridgeTest() {
|
||||||
|
super();
|
||||||
|
circumventionProvider = new CircumventionProvider() {
|
||||||
|
@Override
|
||||||
|
public boolean isTorProbablyBlocked(String countryCode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doBridgesWork(String countryCode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getBridges() {
|
||||||
|
return singletonList(bridges.get(currentBridge));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bridges = new CircumventionProviderImpl(appContext).getBridges();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
IntegrationTestComponent component =
|
||||||
|
DaggerIntegrationTestComponent.builder().build();
|
||||||
|
component.inject(this);
|
||||||
|
|
||||||
|
Executor ioExecutor = Executors.newCachedThreadPool();
|
||||||
|
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
|
||||||
|
LocationUtils locationUtils = () -> "US";
|
||||||
|
SocketFactory torSocketFactory = SocketFactory.getDefault();
|
||||||
|
|
||||||
|
factory = new TorPluginFactory(ioExecutor, scheduler, appContext,
|
||||||
|
locationUtils, eventBus, torSocketFactory,
|
||||||
|
backoffFactory, circumventionProvider, clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBridges() throws Exception {
|
||||||
|
assertTrue(bridges.size() > 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < bridges.size(); i++) {
|
||||||
|
testBridge(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testBridge(int bridge) throws Exception {
|
||||||
|
DuplexPlugin duplexPlugin =
|
||||||
|
factory.createPlugin(new TorPluginCallBack());
|
||||||
|
assertNotNull(duplexPlugin);
|
||||||
|
TorPlugin plugin = (TorPlugin) duplexPlugin;
|
||||||
|
|
||||||
|
currentBridge = bridge;
|
||||||
|
LOG.warning("Testing " + bridges.get(currentBridge));
|
||||||
|
try {
|
||||||
|
plugin.start();
|
||||||
|
long start = clock.currentTimeMillis();
|
||||||
|
while (clock.currentTimeMillis() - start < TIMEOUT) {
|
||||||
|
if (plugin.isRunning()) return;
|
||||||
|
clock.sleep(500);
|
||||||
|
}
|
||||||
|
if (!plugin.isRunning()) {
|
||||||
|
fail("Could not connect to Tor within timeout.");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
plugin.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class TorPluginCallBack implements DuplexPluginCallback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void incomingConnectionCreated(DuplexTransportConnection d) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void outgoingConnectionCreated(ContactId c,
|
||||||
|
DuplexTransportConnection d) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings getSettings() {
|
||||||
|
return new Settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportProperties getLocalProperties() {
|
||||||
|
return new TransportProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mergeSettings(Settings s) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mergeLocalProperties(TransportProperties p) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transportEnabled() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transportDisabled() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,11 +1,25 @@
|
|||||||
package org.briarproject.bramble;
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||||
|
import org.briarproject.bramble.plugin.tor.CircumventionProviderImpl;
|
||||||
import org.briarproject.bramble.system.AndroidSystemModule;
|
import org.briarproject.bramble.system.AndroidSystemModule;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
@Module(includes = {
|
@Module(includes = {
|
||||||
AndroidSystemModule.class
|
AndroidSystemModule.class
|
||||||
})
|
})
|
||||||
public class BrambleAndroidModule {
|
public class BrambleAndroidModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
CircumventionProvider provideCircumventionProvider(Application app) {
|
||||||
|
return new CircumventionProviderImpl(app.getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
|
public interface PowerTestingConstants {
|
||||||
|
|
||||||
|
int WAKE_LOCK_DURATION = 5000;
|
||||||
|
int ALARM_DELAY = 5000;
|
||||||
|
boolean USE_TOR_WAKE_LOCK = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface CircumventionProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
|
||||||
|
*
|
||||||
|
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||||
|
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
|
||||||
|
*/
|
||||||
|
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Countries where vanilla bridge connection are likely to work.
|
||||||
|
* Should be a subset of {@link #BLOCKED}.
|
||||||
|
*/
|
||||||
|
String[] BRIDGES = { "EG", "BY", "TR", "SY", "VE" };
|
||||||
|
|
||||||
|
boolean isTorProbablyBlocked(String countryCode);
|
||||||
|
|
||||||
|
boolean doBridgesWork(String countryCode);
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
List<String> getBridges();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
public class CircumventionProviderImpl implements CircumventionProvider {
|
||||||
|
|
||||||
|
private final static String BRIDGE_FILE_NAME = "bridges";
|
||||||
|
|
||||||
|
private final Context ctx;
|
||||||
|
@Nullable
|
||||||
|
private volatile List<String> bridges = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CircumventionProviderImpl(Context ctx) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Set<String> BLOCKED_IN_COUNTRIES =
|
||||||
|
new HashSet<>(Arrays.asList(BLOCKED));
|
||||||
|
private static final Set<String> BRIDGES_WORK_IN_COUNTRIES =
|
||||||
|
new HashSet<>(Arrays.asList(BRIDGES));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTorProbablyBlocked(String countryCode) {
|
||||||
|
return BLOCKED_IN_COUNTRIES.contains(countryCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doBridgesWork(String countryCode) {
|
||||||
|
return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@IoExecutor
|
||||||
|
public List<String> getBridges() {
|
||||||
|
if (this.bridges != null) return this.bridges;
|
||||||
|
|
||||||
|
Resources res = ctx.getResources();
|
||||||
|
int resId = res.getIdentifier(BRIDGE_FILE_NAME, "raw",
|
||||||
|
ctx.getPackageName());
|
||||||
|
InputStream is = ctx.getResources().openRawResource(resId);
|
||||||
|
Scanner scanner = new Scanner(is);
|
||||||
|
|
||||||
|
List<String> bridges = new ArrayList<>();
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
String line = scanner.nextLine();
|
||||||
|
if (!line.startsWith("#")) bridges.add(line);
|
||||||
|
}
|
||||||
|
scanner.close();
|
||||||
|
this.bridges = bridges;
|
||||||
|
return bridges;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
class TorNetworkMetadata {
|
|
||||||
|
|
||||||
// See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
|
||||||
// and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
|
|
||||||
// TODO: get a more complete list
|
|
||||||
private static final Set<String> BLOCKED_IN_COUNTRIES =
|
|
||||||
new HashSet<>(Arrays.asList("CN", "IR", "SY", "ZZ"));
|
|
||||||
|
|
||||||
static boolean isTorProbablyBlocked(String countryCode) {
|
|
||||||
return BLOCKED_IN_COUNTRIES.contains(countryCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,7 +50,9 @@ import java.io.OutputStream;
|
|||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -83,6 +85,7 @@ import static java.util.logging.Level.INFO;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||||
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
|
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
|
||||||
|
import static org.briarproject.bramble.PowerTestingConstants.USE_TOR_WAKE_LOCK;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||||
@@ -119,6 +122,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private final Backoff backoff;
|
private final Backoff backoff;
|
||||||
private final DuplexPluginCallback callback;
|
private final DuplexPluginCallback callback;
|
||||||
private final String architecture;
|
private final String architecture;
|
||||||
|
private final CircumventionProvider circumventionProvider;
|
||||||
private final int maxLatency, maxIdleTime, socketTimeout;
|
private final int maxLatency, maxIdleTime, socketTimeout;
|
||||||
private final ConnectionStatus connectionStatus;
|
private final ConnectionStatus connectionStatus;
|
||||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||||
@@ -138,7 +142,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
Context appContext, LocationUtils locationUtils,
|
Context appContext, LocationUtils locationUtils,
|
||||||
SocketFactory torSocketFactory, Clock clock, Backoff backoff,
|
SocketFactory torSocketFactory, Clock clock, Backoff backoff,
|
||||||
DuplexPluginCallback callback, String architecture,
|
DuplexPluginCallback callback, String architecture,
|
||||||
int maxLatency, int maxIdleTime) {
|
CircumventionProvider circumventionProvider, int maxLatency, int maxIdleTime) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
@@ -148,6 +152,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
this.backoff = backoff;
|
this.backoff = backoff;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.architecture = architecture;
|
this.architecture = architecture;
|
||||||
|
this.circumventionProvider = circumventionProvider;
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
this.maxIdleTime = maxIdleTime;
|
this.maxIdleTime = maxIdleTime;
|
||||||
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
||||||
@@ -489,12 +494,23 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
private void enableNetwork(boolean enable) throws IOException {
|
private void enableNetwork(boolean enable) throws IOException {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
if (enable) wakeLock.acquire();
|
if (enable && USE_TOR_WAKE_LOCK) wakeLock.acquire();
|
||||||
connectionStatus.enableNetwork(enable);
|
connectionStatus.enableNetwork(enable);
|
||||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||||
if (!enable) {
|
if (!enable) {
|
||||||
callback.transportDisabled();
|
callback.transportDisabled();
|
||||||
wakeLock.release();
|
if (USE_TOR_WAKE_LOCK) wakeLock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableBridges(boolean enable) throws IOException {
|
||||||
|
if (enable) {
|
||||||
|
Collection<String> conf = new ArrayList<>();
|
||||||
|
conf.add("UseBridges 1");
|
||||||
|
conf.addAll(circumventionProvider.getBridges());
|
||||||
|
controlConnection.setConf(conf);
|
||||||
|
} else {
|
||||||
|
controlConnection.setConf("UseBridges", "0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +530,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wakeLock.release();
|
if (USE_TOR_WAKE_LOCK) wakeLock.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -662,8 +678,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
boolean online = net != null && net.isConnected();
|
boolean online = net != null && net.isConnected();
|
||||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||||
String country = locationUtils.getCurrentCountry();
|
String country = locationUtils.getCurrentCountry();
|
||||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
boolean blocked =
|
||||||
country);
|
circumventionProvider.isTorProbablyBlocked(country);
|
||||||
Settings s = callback.getSettings();
|
Settings s = callback.getSettings();
|
||||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
||||||
|
|
||||||
@@ -677,15 +693,22 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (!online) {
|
if (!online) {
|
||||||
LOG.info("Disabling network, device is offline");
|
LOG.info("Disabling network, device is offline");
|
||||||
enableNetwork(false);
|
enableNetwork(false);
|
||||||
} else if (blocked) {
|
|
||||||
LOG.info("Disabling network, country is blocked");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||||
LOG.info("Disabling network due to data setting");
|
LOG.info("Disabling network due to data setting");
|
||||||
enableNetwork(false);
|
enableNetwork(false);
|
||||||
|
} else if (blocked) {
|
||||||
|
if (circumventionProvider.doBridgesWork(country)) {
|
||||||
|
LOG.info("Enabling network, using bridges");
|
||||||
|
enableBridges(true);
|
||||||
|
enableNetwork(true);
|
||||||
|
} else {
|
||||||
|
LOG.info("Disabling network, country is blocked");
|
||||||
|
enableNetwork(false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Enabling network");
|
LOG.info("Enabling network");
|
||||||
|
enableBridges(false);
|
||||||
enableNetwork(true);
|
enableNetwork(true);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -43,12 +43,14 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final SocketFactory torSocketFactory;
|
private final SocketFactory torSocketFactory;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
private final CircumventionProvider circumventionProvider;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
public TorPluginFactory(Executor ioExecutor,
|
public TorPluginFactory(Executor ioExecutor,
|
||||||
ScheduledExecutorService scheduler, Context appContext,
|
ScheduledExecutorService scheduler, Context appContext,
|
||||||
LocationUtils locationUtils, EventBus eventBus,
|
LocationUtils locationUtils, EventBus eventBus,
|
||||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||||
|
CircumventionProvider circumventionProvider,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
@@ -57,6 +59,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.torSocketFactory = torSocketFactory;
|
this.torSocketFactory = torSocketFactory;
|
||||||
this.backoffFactory = backoffFactory;
|
this.backoffFactory = backoffFactory;
|
||||||
|
this.circumventionProvider = circumventionProvider;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +98,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
|
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
|
||||||
locationUtils, torSocketFactory, clock, backoff, callback,
|
locationUtils, torSocketFactory, clock, backoff, callback,
|
||||||
architecture, MAX_LATENCY, MAX_IDLE_TIME);
|
architecture, circumventionProvider, MAX_LATENCY, MAX_IDLE_TIME);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|||||||
5
bramble-android/src/main/res/raw/bridges
Normal file
5
bramble-android/src/main/res/raw/bridges
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Bridge 131.252.210.150:8081 0E858AC201BF0F3FA3C462F64844CBFFC7297A42
|
||||||
|
Bridge 67.205.189.122:8443 12D64D5D44E20169585E7378580C0D33A872AD98
|
||||||
|
Bridge 45.32.148.146:8443 0CE016FB2462D8BF179AE71F7D702D09DEAC3F1D
|
||||||
|
Bridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC
|
||||||
|
#Bridge 128.105.214.161:8081 1E326AAFB3FCB515015250D8FCCC8E37F91A153B
|
||||||
@@ -12,7 +12,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation for injecting the executor for long-running IO tasks. Also used
|
* Annotation for injecting the executor for long-running IO tasks. Also used
|
||||||
* for annotating methods that should run on the UI executor.
|
* for annotating methods that should run on the IO executor.
|
||||||
* <p>
|
* <p>
|
||||||
* The contract of this executor is that tasks may be run concurrently, and
|
* The contract of this executor is that tasks may be run concurrently, and
|
||||||
* submitting a task will never block. Tasks may run indefinitely. Tasks
|
* submitting a task will never block. Tasks may run indefinitely. Tasks
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 10010
|
versionCode 10011
|
||||||
versionName "1.0.10"
|
versionName "1.0.11"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
resValue "string", "app_package", "org.briarproject.briar.android"
|
resValue "string", "app_package", "org.briarproject.briar.android"
|
||||||
resValue "string", "app_name", "Briar"
|
resValue "string", "app_name", "Briar"
|
||||||
@@ -261,7 +261,7 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
shrinkResources true
|
shrinkResources false
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
crunchPngs false
|
crunchPngs false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.api.system.Scheduler;
|
import org.briarproject.bramble.api.system.Scheduler;
|
||||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||||
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.android.DozeWatchdog;
|
import org.briarproject.briar.api.android.DozeWatchdog;
|
||||||
import org.briarproject.briar.api.android.ReferenceManager;
|
|
||||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -99,14 +99,14 @@ public class AppModule {
|
|||||||
AndroidExecutor androidExecutor, SecureRandom random,
|
AndroidExecutor androidExecutor, SecureRandom random,
|
||||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||||
Application app, LocationUtils locationUtils, EventBus eventBus,
|
Application app, LocationUtils locationUtils, EventBus eventBus,
|
||||||
Clock clock) {
|
CircumventionProvider circumventionProvider, Clock clock) {
|
||||||
Context appContext = app.getApplicationContext();
|
Context appContext = app.getApplicationContext();
|
||||||
DuplexPluginFactory bluetooth =
|
DuplexPluginFactory bluetooth =
|
||||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||||
appContext, random, eventBus, backoffFactory);
|
appContext, random, eventBus, backoffFactory);
|
||||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||||
appContext, locationUtils, eventBus, torSocketFactory,
|
appContext, locationUtils, eventBus, torSocketFactory,
|
||||||
backoffFactory, clock);
|
backoffFactory, circumventionProvider, clock);
|
||||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||||
scheduler, backoffFactory, appContext);
|
scheduler, backoffFactory, appContext);
|
||||||
Collection<DuplexPluginFactory> duplex = asList(bluetooth, tor, lan);
|
Collection<DuplexPluginFactory> duplex = asList(bluetooth, tor, lan);
|
||||||
@@ -165,12 +165,6 @@ public class AppModule {
|
|||||||
return app.getSharedPreferences("db", MODE_PRIVATE);
|
return app.getSharedPreferences("db", MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
ReferenceManager provideReferenceManager() {
|
|
||||||
return new ReferenceManagerImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
AndroidNotificationManager provideAndroidNotificationManager(
|
AndroidNotificationManager provideAndroidNotificationManager(
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ public class BriarApplicationImpl extends Application
|
|||||||
BrambleCoreModule.initEagerSingletons(applicationComponent);
|
BrambleCoreModule.initEagerSingletons(applicationComponent);
|
||||||
BriarCoreModule.initEagerSingletons(applicationComponent);
|
BriarCoreModule.initEagerSingletons(applicationComponent);
|
||||||
AndroidEagerSingletons.initEagerSingletons(applicationComponent);
|
AndroidEagerSingletons.initEagerSingletons(applicationComponent);
|
||||||
|
new SleepMonitor().start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.support.v4.content.WakefulBroadcastReceiver;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
|
import static android.content.Context.POWER_SERVICE;
|
||||||
|
import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED;
|
||||||
|
import static android.content.Intent.ACTION_BATTERY_CHANGED;
|
||||||
|
import static android.content.Intent.ACTION_POWER_CONNECTED;
|
||||||
|
import static android.content.Intent.ACTION_POWER_DISCONNECTED;
|
||||||
|
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||||
|
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||||
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
|
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||||
|
import static android.os.BatteryManager.EXTRA_LEVEL;
|
||||||
|
import static android.os.BatteryManager.EXTRA_PLUGGED;
|
||||||
|
import static android.os.BatteryManager.EXTRA_SCALE;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||||
|
import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED;
|
||||||
|
|
||||||
|
public class BriarBroadcastReceiver extends WakefulBroadcastReceiver {
|
||||||
|
|
||||||
|
IntentFilter getIntentFilter() {
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_SCREEN_ON);
|
||||||
|
filter.addAction(ACTION_SCREEN_OFF);
|
||||||
|
filter.addAction(ACTION_BATTERY_CHANGED);
|
||||||
|
filter.addAction(ACTION_POWER_CONNECTED);
|
||||||
|
filter.addAction(ACTION_POWER_DISCONNECTED);
|
||||||
|
if (SDK_INT >= 21) filter.addAction(ACTION_POWER_SAVE_MODE_CHANGED);
|
||||||
|
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||||
|
filter.addAction(ACTION_AIRPLANE_MODE_CHANGED);
|
||||||
|
filter.addAction(CONNECTIVITY_ACTION);
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctx, Intent i) {
|
||||||
|
String action = i.getAction();
|
||||||
|
if (ACTION_SCREEN_ON.equals(action)) {
|
||||||
|
Log.i("DEVICE_STATUS", "Screen on");
|
||||||
|
} else if (ACTION_SCREEN_OFF.equals(action)) {
|
||||||
|
Log.i("DEVICE_STATUS", "Screen off");
|
||||||
|
} else if (ACTION_BATTERY_CHANGED.equals(action)) {
|
||||||
|
int level = i.getIntExtra(EXTRA_LEVEL, -1);
|
||||||
|
int scale = i.getIntExtra(EXTRA_SCALE, -1);
|
||||||
|
int plugged = i.getIntExtra(EXTRA_PLUGGED, -1);
|
||||||
|
Log.i("DEVICE_STATUS", "Battery level: " + (level / (float) scale)
|
||||||
|
+ ", plugged: " + (plugged != 0));
|
||||||
|
} else if (ACTION_POWER_CONNECTED.equals(action)) {
|
||||||
|
Log.i("DEVICE_STATUS", "Power connected");
|
||||||
|
} else if (ACTION_POWER_DISCONNECTED.equals(action)) {
|
||||||
|
Log.i("DEVICE_STATUS", "Power disconnected");
|
||||||
|
} else if (SDK_INT >= 21
|
||||||
|
&& ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) {
|
||||||
|
PowerManager pm = (PowerManager)
|
||||||
|
ctx.getSystemService(POWER_SERVICE);
|
||||||
|
Log.i("DEVICE_STATUS", "Power save mode: " + pm.isPowerSaveMode());
|
||||||
|
} else if (SDK_INT >= 23
|
||||||
|
&& ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
|
||||||
|
PowerManager pm = (PowerManager)
|
||||||
|
ctx.getSystemService(POWER_SERVICE);
|
||||||
|
Log.i("DEVICE_STATUS", " Idle mode: " + pm.isDeviceIdleMode());
|
||||||
|
} else if (ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
|
||||||
|
Log.i("DEVICE_STATUS",
|
||||||
|
"Airplane mode: " + i.getBooleanExtra("state", false));
|
||||||
|
} else if (CONNECTIVITY_ACTION.equals(action)) {
|
||||||
|
ConnectivityManager cm = (ConnectivityManager)
|
||||||
|
ctx.getSystemService(CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||||
|
boolean online = net != null && net.isConnected();
|
||||||
|
boolean wifi = net != null && net.getType() == TYPE_WIFI;
|
||||||
|
Log.i("DEVICE_STATUS", "Online: " + online + ", wifi: " + wifi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android;
|
|||||||
|
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.ActivityManager.RunningAppProcessInfo;
|
import android.app.ActivityManager.RunningAppProcessInfo;
|
||||||
|
import android.app.AlarmManager;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@@ -13,9 +14,14 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.os.PowerManager.WakeLock;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
@@ -33,8 +39,10 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||||
|
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
||||||
|
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
|
||||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
import static android.content.Intent.ACTION_SHUTDOWN;
|
import static android.content.Intent.ACTION_SHUTDOWN;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||||
@@ -43,11 +51,15 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
|||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
||||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
||||||
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
||||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.PowerTestingConstants.ALARM_DELAY;
|
||||||
|
import static org.briarproject.bramble.PowerTestingConstants.USE_TOR_WAKE_LOCK;
|
||||||
|
import static org.briarproject.bramble.PowerTestingConstants.WAKE_LOCK_DURATION;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
|
||||||
@@ -65,15 +77,27 @@ public class BriarService extends Service {
|
|||||||
public static String EXTRA_STARTUP_FAILED =
|
public static String EXTRA_STARTUP_FAILED =
|
||||||
"org.briarproject.briar.STARTUP_FAILED";
|
"org.briarproject.briar.STARTUP_FAILED";
|
||||||
|
|
||||||
|
// This tag prevents the wake lock from being ignored on some Huawei devices
|
||||||
|
private static final String WAKE_LOCK_TAG = "LocationManagerService";
|
||||||
|
private static final String ACTION_ALARM =
|
||||||
|
"org.briarproject.briar.android.ACTION_ALARM";
|
||||||
|
private static final String EXTRA_DUE_MILLIS =
|
||||||
|
"org.briarproject.briar.android.DUE_MILLIS";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BriarService.class.getName());
|
Logger.getLogger(BriarService.class.getName());
|
||||||
|
|
||||||
private final AtomicBoolean created = new AtomicBoolean(false);
|
private final AtomicBoolean created = new AtomicBoolean(false);
|
||||||
private final Binder binder = new BriarBinder();
|
private final Binder binder = new BriarBinder();
|
||||||
|
|
||||||
|
private AlarmManager alarm;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private BroadcastReceiver receiver = null;
|
private BroadcastReceiver receiver = null;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BriarBroadcastReceiver testReceiver = null;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected DatabaseConfig databaseConfig;
|
protected DatabaseConfig databaseConfig;
|
||||||
// Fields that are accessed from background threads must be volatile
|
// Fields that are accessed from background threads must be volatile
|
||||||
@@ -90,6 +114,11 @@ public class BriarService extends Service {
|
|||||||
BriarApplication application = (BriarApplication) getApplication();
|
BriarApplication application = (BriarApplication) getApplication();
|
||||||
application.getApplicationComponent().inject(this);
|
application.getApplicationComponent().inject(this);
|
||||||
|
|
||||||
|
alarm = (AlarmManager)
|
||||||
|
getApplicationContext().getSystemService(ALARM_SERVICE);
|
||||||
|
|
||||||
|
setAlarm();
|
||||||
|
|
||||||
LOG.info("Created");
|
LOG.info("Created");
|
||||||
if (created.getAndSet(true)) {
|
if (created.getAndSet(true)) {
|
||||||
LOG.info("Already created");
|
LOG.info("Already created");
|
||||||
@@ -168,6 +197,34 @@ public class BriarService extends Service {
|
|||||||
filter.addAction("android.intent.action.QUICKBOOT_POWEROFF");
|
filter.addAction("android.intent.action.QUICKBOOT_POWEROFF");
|
||||||
filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF");
|
filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF");
|
||||||
registerReceiver(receiver, filter);
|
registerReceiver(receiver, filter);
|
||||||
|
testReceiver = new BriarBroadcastReceiver();
|
||||||
|
registerReceiver(testReceiver, testReceiver.getIntentFilter());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAlarm() {
|
||||||
|
long dueMillis = SystemClock.elapsedRealtime() + ALARM_DELAY;
|
||||||
|
PendingIntent pi = getPendingIntent(dueMillis);
|
||||||
|
if (SDK_INT >= 23) {
|
||||||
|
alarm.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP,
|
||||||
|
dueMillis, pi);
|
||||||
|
} else if (SDK_INT >= 19) {
|
||||||
|
alarm.setExact(ELAPSED_REALTIME_WAKEUP, dueMillis, pi);
|
||||||
|
} else {
|
||||||
|
alarm.set(ELAPSED_REALTIME_WAKEUP, dueMillis, pi);
|
||||||
|
}
|
||||||
|
Log.i("ALARM_TEST", "Alarm set for " + ALARM_DELAY + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingIntent getPendingIntent(long dueMillis) {
|
||||||
|
return PendingIntent.getService(getApplicationContext(), 0,
|
||||||
|
getAlarmIntent(dueMillis), FLAG_CANCEL_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent getAlarmIntent(long dueMillis) {
|
||||||
|
Intent i = new Intent(getApplicationContext(), BriarService.class);
|
||||||
|
i.setAction(ACTION_ALARM);
|
||||||
|
i.putExtra(EXTRA_DUE_MILLIS, dueMillis);
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -204,6 +261,30 @@ public class BriarService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (ACTION_ALARM.equals(intent.getAction())) {
|
||||||
|
long dueMillis = intent.getLongExtra(EXTRA_DUE_MILLIS, 0);
|
||||||
|
long late = SystemClock.elapsedRealtime() - dueMillis;
|
||||||
|
Log.i("ALARM_TEST", "Alarm fired " + late + " ms late");
|
||||||
|
PowerManager powerManager = (PowerManager)
|
||||||
|
getApplicationContext().getSystemService(POWER_SERVICE);
|
||||||
|
WakeLock wakeLock = powerManager.newWakeLock(PARTIAL_WAKE_LOCK,
|
||||||
|
WAKE_LOCK_TAG);
|
||||||
|
if (!USE_TOR_WAKE_LOCK) {
|
||||||
|
//acquire wakelock
|
||||||
|
wakeLock.acquire();
|
||||||
|
Log.i("ALARM_TEST", "WakeLock acquired for "
|
||||||
|
+ WAKE_LOCK_DURATION + " ms");
|
||||||
|
}
|
||||||
|
new Handler().postDelayed(() -> {
|
||||||
|
//set alarm before releasing wake lock
|
||||||
|
setAlarm();
|
||||||
|
if (!USE_TOR_WAKE_LOCK) {
|
||||||
|
//release wakelock
|
||||||
|
Log.i("ALARM_TEST", "Releasing WakeLock");
|
||||||
|
wakeLock.release();
|
||||||
|
}
|
||||||
|
}, WAKE_LOCK_DURATION);
|
||||||
|
}
|
||||||
return START_NOT_STICKY; // Don't restart automatically if killed
|
return START_NOT_STICKY; // Don't restart automatically if killed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,10 +299,12 @@ public class BriarService extends Service {
|
|||||||
LOG.info("Destroyed");
|
LOG.info("Destroyed");
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
if (receiver != null) unregisterReceiver(receiver);
|
if (receiver != null) unregisterReceiver(receiver);
|
||||||
|
if (testReceiver != null) unregisterReceiver(testReceiver);
|
||||||
// Stop the services in a background thread
|
// Stop the services in a background thread
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
if (started) lifecycleManager.stopServices();
|
if (started) lifecycleManager.stopServices();
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
package org.briarproject.briar.android;
|
|
||||||
|
|
||||||
import org.briarproject.briar.api.android.ReferenceManager;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
|
|
||||||
class ReferenceManagerImpl implements ReferenceManager {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(ReferenceManagerImpl.class.getName());
|
|
||||||
|
|
||||||
private final Lock lock = new ReentrantLock();
|
|
||||||
|
|
||||||
// The following are locking: lock
|
|
||||||
private final Map<Class<?>, Map<Long, Object>> outerMap = new HashMap<>();
|
|
||||||
private long nextHandle = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> T getReference(long handle, Class<T> c) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
Map<Long, Object> innerMap = outerMap.get(c);
|
|
||||||
if (innerMap == null) {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("0 handles for " + c.getName());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info(innerMap.size() + " handles for " + c.getName());
|
|
||||||
Object o = innerMap.get(handle);
|
|
||||||
return c.cast(o);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> long putReference(T reference, Class<T> c) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
Map<Long, Object> innerMap = outerMap.get(c);
|
|
||||||
if (innerMap == null) {
|
|
||||||
innerMap = new HashMap<>();
|
|
||||||
outerMap.put(c, innerMap);
|
|
||||||
}
|
|
||||||
long handle = nextHandle++;
|
|
||||||
innerMap.put(handle, reference);
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info(innerMap.size() + " handles for " + c.getName() +
|
|
||||||
" after put");
|
|
||||||
}
|
|
||||||
return handle;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> T removeReference(long handle, Class<T> c) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
Map<Long, Object> innerMap = outerMap.get(c);
|
|
||||||
if (innerMap == null) return null;
|
|
||||||
Object o = innerMap.remove(handle);
|
|
||||||
if (innerMap.isEmpty()) outerMap.remove(c);
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info(innerMap.size() + " handles for " + c.getName() +
|
|
||||||
" after remove");
|
|
||||||
}
|
|
||||||
return c.cast(o);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
import static java.util.Locale.US;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
|
public class SleepMonitor implements Runnable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How often to check the uptime and real time.
|
||||||
|
*/
|
||||||
|
private static final int INTERVAL_MS = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the difference between uptime and real time changes by more than
|
||||||
|
* this amount, assume deep sleep has occurred.
|
||||||
|
*/
|
||||||
|
private static final int MIN_SLEEP_DURATION_MS = 1000;
|
||||||
|
|
||||||
|
private final ScheduledExecutorService executorService;
|
||||||
|
|
||||||
|
private volatile long uptime, realtime, diff;
|
||||||
|
|
||||||
|
SleepMonitor() {
|
||||||
|
uptime = SystemClock.uptimeMillis();
|
||||||
|
realtime = SystemClock.elapsedRealtime();
|
||||||
|
diff = realtime - uptime;
|
||||||
|
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
executorService.scheduleAtFixedRate(this, 0, INTERVAL_MS, MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
long lastRealtime = realtime;
|
||||||
|
long sleepDuration = getSleepDuration();
|
||||||
|
if (sleepDuration > MIN_SLEEP_DURATION_MS) {
|
||||||
|
long elapsed = realtime - lastRealtime;
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
String earliestStart = getTime(now - elapsed);
|
||||||
|
String earliestEnd = getTime(now - elapsed + sleepDuration);
|
||||||
|
String latestStart = getTime(now - sleepDuration);
|
||||||
|
String latestEnd = getTime(now);
|
||||||
|
Log.i("SLEEP_INFO", "System slept for " + sleepDuration
|
||||||
|
+ " ms since last check " + elapsed
|
||||||
|
+ " ms ago (earliest " + earliestStart + " - "
|
||||||
|
+ earliestEnd + ", latest " + latestStart + " - "
|
||||||
|
+ latestEnd + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the amount of time spent in deep sleep since the last check.
|
||||||
|
*/
|
||||||
|
private long getSleepDuration() {
|
||||||
|
uptime = SystemClock.uptimeMillis();
|
||||||
|
realtime = SystemClock.elapsedRealtime();
|
||||||
|
long lastDiff = diff;
|
||||||
|
diff = realtime - uptime;
|
||||||
|
return diff - lastDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTime(long time) {
|
||||||
|
DateFormat sdf = new SimpleDateFormat("HH:mm:ss", US);
|
||||||
|
return sdf.format(new Date(time));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -177,7 +177,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
// FIXME #824
|
// FIXME #824
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
|
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
|
||||||
BaseFragment f = KeyAgreementFragment.newInstance(this);
|
BaseFragment f = KeyAgreementFragment.newInstance();
|
||||||
fm.beginTransaction()
|
fm.beginTransaction()
|
||||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||||
.addToBackStack(f.getUniqueTag())
|
.addToBackStack(f.getUniqueTag())
|
||||||
|
|||||||
@@ -91,15 +91,19 @@ public class KeyAgreementFragment extends BaseEventFragment
|
|||||||
private KeyAgreementTask task;
|
private KeyAgreementTask task;
|
||||||
private KeyAgreementEventListener listener;
|
private KeyAgreementEventListener listener;
|
||||||
|
|
||||||
public static KeyAgreementFragment newInstance(
|
public static KeyAgreementFragment newInstance() {
|
||||||
KeyAgreementEventListener listener) {
|
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
KeyAgreementFragment fragment = new KeyAgreementFragment();
|
KeyAgreementFragment fragment = new KeyAgreementFragment();
|
||||||
fragment.listener = listener;
|
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
listener = (KeyAgreementEventListener) context;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package org.briarproject.briar.api.android;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages mappings between object references and serialisable handles. This
|
|
||||||
* enables references to be passed between Android UI objects that belong to
|
|
||||||
* the same process but can only communicate via serialisation.
|
|
||||||
*/
|
|
||||||
public interface ReferenceManager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the object with the given handle, or null if no mapping exists
|
|
||||||
* for the handle.
|
|
||||||
*/
|
|
||||||
<T> T getReference(long handle, Class<T> c);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a mapping between the given reference and a handle, and returns
|
|
||||||
* the handle.
|
|
||||||
*/
|
|
||||||
<T> long putReference(T reference, Class<T> c);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes and returns the object with the given handle, or returns null
|
|
||||||
* if no mapping exists for the handle.
|
|
||||||
*/
|
|
||||||
<T> T removeReference(long handle, Class<T> c);
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,9 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:hint="@string/blogs_rss_feeds_import_hint"
|
android:hint="@string/blogs_rss_feeds_import_hint"
|
||||||
android:inputType="textUri"/>
|
android:inputType="textUri"
|
||||||
|
android:paddingLeft="@dimen/margin_large"
|
||||||
|
android:paddingRight="@dimen/margin_large"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/importButton"
|
android:id="@+id/importButton"
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:paddingLeft="@dimen/margin_large"
|
||||||
|
android:paddingRight="@dimen/margin_large"
|
||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|||||||
@@ -62,8 +62,8 @@
|
|||||||
<string name="transport_bt">Bluetooth</string>
|
<string name="transport_bt">Bluetooth</string>
|
||||||
<string name="transport_lan">Wi-Fi</string>
|
<string name="transport_lan">Wi-Fi</string>
|
||||||
<!--Notifications-->
|
<!--Notifications-->
|
||||||
<string name="reminder_notification_title">Has sortit de Briar</string>
|
<string name="reminder_notification_title">Heu sortit de Briar</string>
|
||||||
<string name="reminder_notification_text">Toqueu per tornar a iniciar sessió o feu lliscar el dit per descartar.</string>
|
<string name="reminder_notification_text">Toqueu per reiniciar la sessió o feu lliscar el dit per descartar-la.</string>
|
||||||
<string name="reminder_notification_channel_title">Recordatori d\'inici de sessió de Briar</string>
|
<string name="reminder_notification_channel_title">Recordatori d\'inici de sessió de Briar</string>
|
||||||
<string name="ongoing_notification_title">Sessió iniciada</string>
|
<string name="ongoing_notification_title">Sessió iniciada</string>
|
||||||
<string name="ongoing_notification_text">Toca per a obrir Briar.</string>
|
<string name="ongoing_notification_text">Toca per a obrir Briar.</string>
|
||||||
@@ -224,8 +224,8 @@
|
|||||||
<item quantity="one">%d publicacio</item>
|
<item quantity="one">%d publicacio</item>
|
||||||
<item quantity="other">%d apunts</item>
|
<item quantity="other">%d apunts</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="forum_new_entry_posted">Publicació del fòrum publicada</string>
|
<string name="forum_new_entry_posted">S\'ha publicat l\'apunt al fòrum</string>
|
||||||
<string name="forum_new_message_hint">Nova publicació</string>
|
<string name="forum_new_message_hint">Apunt nou</string>
|
||||||
<string name="forum_message_reply_hint">Resposta nova</string>
|
<string name="forum_message_reply_hint">Resposta nova</string>
|
||||||
<string name="btn_reply">Respon</string>
|
<string name="btn_reply">Respon</string>
|
||||||
<string name="forum_leave">Abandona el fòrum</string>
|
<string name="forum_leave">Abandona el fòrum</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user