mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
147 Commits
earthlingI
...
offline-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a438050e68 | ||
|
|
4d0fe24722 | ||
|
|
559b29e8b5 | ||
|
|
1b7b285862 | ||
|
|
178810241f | ||
|
|
61c601cb6d | ||
|
|
2002ad08ca | ||
|
|
d134a67ee9 | ||
|
|
04cf8e16a9 | ||
|
|
227d345858 | ||
|
|
07ef73ab56 | ||
|
|
ea2b1ff4d8 | ||
|
|
69fac86a0c | ||
|
|
bd6b6c1cd6 | ||
|
|
be6c868135 | ||
|
|
041a296666 | ||
|
|
276eeb1c20 | ||
|
|
d46cfb757e | ||
|
|
d81c4e7982 | ||
|
|
8c4d6ed5e4 | ||
|
|
b21b319cb7 | ||
|
|
780f6e97b9 | ||
|
|
1756215183 | ||
|
|
7e3db6c6df | ||
|
|
1adf408ade | ||
|
|
d662ae49ee | ||
|
|
7939c8b213 | ||
|
|
1899873da3 | ||
|
|
5beffb21f1 | ||
|
|
0f9afda329 | ||
|
|
c16663b530 | ||
|
|
77767b45c9 | ||
|
|
79ae8fea8d | ||
|
|
7e3eb1201a | ||
|
|
eb283d81c5 | ||
|
|
be3700d364 | ||
|
|
ccec17f28a | ||
|
|
e8428925ae | ||
|
|
195123e669 | ||
|
|
abe570e905 | ||
|
|
a93b1f18ac | ||
|
|
e4bd6fdf95 | ||
|
|
793d81bd93 | ||
|
|
be637cef65 | ||
|
|
9370062e41 | ||
|
|
8c1f721015 | ||
|
|
22ea4ced0d | ||
|
|
312d31b40e | ||
|
|
b15d42b0cd | ||
|
|
9274b8ef4a | ||
|
|
7ef4ea51b3 | ||
|
|
e285f21d1c | ||
|
|
160cef25af | ||
|
|
c0293a1327 | ||
|
|
035c639aa0 | ||
|
|
29d31e79c3 | ||
|
|
7f7210becd | ||
|
|
ce74fcaab5 | ||
|
|
d4c1e132f7 | ||
|
|
6b976df6a8 | ||
|
|
3e4db3b9da | ||
|
|
0bf59eec20 | ||
|
|
9f828a2222 | ||
|
|
7be77b8c60 | ||
|
|
d5853e8403 | ||
|
|
32e9bf01ec | ||
|
|
a5ce400341 | ||
|
|
a960bfb2c1 | ||
|
|
847650f280 | ||
|
|
77a3199aac | ||
|
|
9a58b37ce2 | ||
|
|
608e1eac6b | ||
|
|
09de768e7e | ||
|
|
faab80f0ea | ||
|
|
07162cad8b | ||
|
|
a5a1cdfabb | ||
|
|
bfcb469d49 | ||
|
|
f8b645d2b1 | ||
|
|
052eb03c9e | ||
|
|
83bf3f4ca7 | ||
|
|
f9181fa021 | ||
|
|
79dae27c24 | ||
|
|
ac0fc21e6e | ||
|
|
dab736ce0e | ||
|
|
8ef21637a9 | ||
|
|
e3a1fca22e | ||
|
|
ea9a2789ab | ||
|
|
cbdbd10cb3 | ||
|
|
d6f985174a | ||
|
|
d184fbd3fe | ||
|
|
ef623370b6 | ||
|
|
5ac636d52d | ||
|
|
f1c71ec5a7 | ||
|
|
5cc280be61 | ||
|
|
a5d8faef3c | ||
|
|
e22e9dcade | ||
|
|
7474ad8606 | ||
|
|
1c3d90f7fc | ||
|
|
6f8d7167db | ||
|
|
99da50d37c | ||
|
|
15f5c8deee | ||
|
|
7913cd322e | ||
|
|
de8ad8f6f9 | ||
|
|
d0bc17e634 | ||
|
|
85433611a5 | ||
|
|
ebd5879761 | ||
|
|
b255ab07ae | ||
|
|
a86ba50dec | ||
|
|
bcbc96dc2d | ||
|
|
a72e92de24 | ||
|
|
1ddcd6cfff | ||
|
|
5dfd9e3546 | ||
|
|
e05575b956 | ||
|
|
fd810f5c16 | ||
|
|
3f5e131250 | ||
|
|
3ee516599d | ||
|
|
c703d90636 | ||
|
|
e228b9fcbf | ||
|
|
6e6cadd3ad | ||
|
|
9cc8d44778 | ||
|
|
ee6f571c31 | ||
|
|
2ac3bdd3ae | ||
|
|
e35ffe0cf0 | ||
|
|
8a04d8edc4 | ||
|
|
a5fb3bb4a4 | ||
|
|
eae329cdfa | ||
|
|
0ce0551f0d | ||
|
|
a198e7d08e | ||
|
|
bca6f1506e | ||
|
|
e420201b00 | ||
|
|
03248d04e5 | ||
|
|
2c39b02644 | ||
|
|
c9c6f3682c | ||
|
|
8f4a0ef030 | ||
|
|
5fe22bcd57 | ||
|
|
b4880af7e2 | ||
|
|
51d21bd669 | ||
|
|
b8f3728a0d | ||
|
|
bbfd4f137d | ||
|
|
7e3ca76dd1 | ||
|
|
524c8d26f8 | ||
|
|
7eccf7dac1 | ||
|
|
0bc06248ed | ||
|
|
c999f05cc7 | ||
|
|
428269b312 | ||
|
|
588e05ce83 | ||
|
|
f7875c99b6 |
@@ -33,7 +33,7 @@ test:
|
||||
stage: test
|
||||
script:
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
when: always
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Summary
|
||||
|
||||
(Briefly describe the bug encontered)
|
||||
|
||||
# Steps to reproduce
|
||||
|
||||
(Please list the steps you performed when you encountered the bug)
|
||||
|
||||
-
|
||||
-
|
||||
-
|
||||
|
||||
# How often does it happen?
|
||||
|
||||
(Every time, Sometimes, It happened only once)
|
||||
|
||||
# What did you expect to happen
|
||||
|
||||
(Describe the app behaviour that you expected to happen)
|
||||
|
||||
# What actually happened?
|
||||
|
||||
(Describe what actually happened)
|
||||
|
||||
* What version of Briar app do you have installed? [Please fill in]
|
||||
* Did you send the feedback from your device? [Yes/No]
|
||||
* Your device manufacturer: [Please fill in]
|
||||
* Your device model: [Please fill in]
|
||||
* Android version: [Please fill in]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Are you requesting a new feature or an improvement to an existing feature?
|
||||
|
||||
# Brief description of the new or improved feature you would like to see in the Briar app
|
||||
|
||||
|
||||
# Please describe in detail what you would like to do in Briar app, for which a new or improved feature is required?
|
||||
|
||||
|
||||
* Does the absence of this feature prevent you from using the Briar app for certain activities? What are those activities?
|
||||
|
||||
* Would your use of Briar app increase with this new feature?
|
||||
|
||||
|
||||
|
||||
|
||||
29
README.md
29
README.md
@@ -1,28 +1 @@
|
||||
# Briar
|
||||
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate.
|
||||
|
||||
Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices.
|
||||
|
||||
If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
|
||||
|
||||
## Download Briar
|
||||
|
||||
[<img src="https://briarproject.org//img/fdroid_badge.png" width="240">](https://briarproject.org/fdroid)
|
||||
[<img src="https://briarproject.org/img/google_play_badge_web_generic.png" width="240">](https://play.google.com/store/apps/details?id=org.briarproject.briar.android)
|
||||
|
||||
You can also [download the APK file](https://briarproject.org/apk) directly from
|
||||
our site.
|
||||
|
||||
## Useful links
|
||||
[briarproject.org](https://briarproject.org/)
|
||||
|
||||
[Source code](https://code.briarproject.org/briar/briar/tree/master)
|
||||
|
||||
[Manual](https://briarproject.org/manual/)
|
||||
|
||||
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
|
||||
|
||||
## Donate
|
||||
|
||||
[](https://liberapay.com/Briar/donate) [](https://flattr.com/t/592836/)
|
||||
Bitcoin and BCH: 1NZCKkUCtJV2U2Y9hDb9uq8S7ksFCFGR6K
|
||||
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping the information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
|
||||
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10306
|
||||
versionName "1.3.6"
|
||||
versionCode 10305
|
||||
versionName "1.3.5"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -20,7 +20,7 @@ import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.I
|
||||
public class AndroidRemovableDrivePluginFactory implements
|
||||
SimplexPluginFactory {
|
||||
|
||||
private static final long MAX_LATENCY = DAYS.toMillis(28);
|
||||
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
||||
|
||||
private final Application app;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.net.SocketFactory;
|
||||
@@ -49,7 +48,6 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
|
||||
@NotNullByDefault
|
||||
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
@@ -57,13 +55,6 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
private static final Logger LOG =
|
||||
getLogger(AndroidLanTcpPlugin.class.getName());
|
||||
|
||||
/**
|
||||
* The interface name is used as a heuristic for deciding whether the
|
||||
* device is providing a wifi access point.
|
||||
*/
|
||||
private static final Pattern AP_INTERFACE_NAME =
|
||||
Pattern.compile("^(wlan|ap|p2p)[-0-9]");
|
||||
|
||||
private final Executor connectionStatusExecutor;
|
||||
private final ConnectivityManager connectivityManager;
|
||||
@Nullable
|
||||
@@ -139,14 +130,17 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
if (info != null && info.getIpAddress() != 0) {
|
||||
return new Pair<>(intToInetAddress(info.getIpAddress()), false);
|
||||
}
|
||||
// If we're providing an access point, return its address
|
||||
for (NetworkInterface iface : getNetworkInterfaces()) {
|
||||
if (AP_INTERFACE_NAME.matcher(iface.getName()).find()) {
|
||||
for (InterfaceAddress ifAddr : iface.getInterfaceAddresses()) {
|
||||
if (isPossibleWifiApInterface(ifAddr)) {
|
||||
return new Pair<>(ifAddr.getAddress(), true);
|
||||
}
|
||||
}
|
||||
List<InterfaceAddress> ifAddrs = getLocalInterfaceAddresses();
|
||||
// If we're providing a normal access point, return its address
|
||||
for (InterfaceAddress ifAddr : ifAddrs) {
|
||||
if (isAndroidWifiApAddress(ifAddr)) {
|
||||
return new Pair<>(ifAddr.getAddress(), true);
|
||||
}
|
||||
}
|
||||
// If we're providing a wifi direct access point, return its address
|
||||
for (InterfaceAddress ifAddr : ifAddrs) {
|
||||
if (isAndroidWifiDirectApAddress(ifAddr)) {
|
||||
return new Pair<>(ifAddr.getAddress(), true);
|
||||
}
|
||||
}
|
||||
// Not connected to wifi
|
||||
@@ -154,18 +148,33 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given address may belong to an interface providing
|
||||
* a wifi access point (including wifi direct legacy mode access points).
|
||||
* Returns true if the given address belongs to a network provided by an
|
||||
* Android access point (including the access point's own address).
|
||||
* <p>
|
||||
* This method may return true for wifi client interfaces as well, but
|
||||
* we've already checked for a wifi client connection above.
|
||||
* The access point's address is usually 192.168.43.1, but at least one
|
||||
* device (Honor 8A) may use other addresses in the range 192.168.43.0/24.
|
||||
*/
|
||||
private boolean isPossibleWifiApInterface(InterfaceAddress ifAddr) {
|
||||
private boolean isAndroidWifiApAddress(InterfaceAddress ifAddr) {
|
||||
if (ifAddr.getNetworkPrefixLength() != 24) return false;
|
||||
byte[] ip = ifAddr.getAddress().getAddress();
|
||||
return ip.length == 4
|
||||
&& ip[0] == (byte) 192
|
||||
&& ip[1] == (byte) 168;
|
||||
&& ip[1] == (byte) 168
|
||||
&& ip[2] == (byte) 43;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given address belongs to a network provided by an
|
||||
* Android wifi direct legacy mode access point (including the access
|
||||
* point's own address).
|
||||
*/
|
||||
private boolean isAndroidWifiDirectApAddress(InterfaceAddress ifAddr) {
|
||||
if (ifAddr.getNetworkPrefixLength() != 24) return false;
|
||||
byte[] ip = ifAddr.getAddress().getAddress();
|
||||
return ip.length == 4
|
||||
&& ip[0] == (byte) 192
|
||||
&& ip[1] == (byte) 168
|
||||
&& ip[2] == (byte) 49;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface FeatureFlags {
|
||||
|
||||
boolean shouldEnableConnectViaBluetooth();
|
||||
|
||||
boolean shouldEnableTransferData();
|
||||
|
||||
boolean shouldEnableShareAppViaOfflineHotspot();
|
||||
|
||||
boolean shouldEnableTransferData();
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ public interface RemovableDriveTask extends Runnable {
|
||||
TransportProperties getTransportProperties();
|
||||
|
||||
/**
|
||||
* Adds an observer to the task. The observer will be notified on the
|
||||
* event thread of the current state of the task and any subsequent state
|
||||
* changes.
|
||||
* 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);
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.briarproject.bramble.util;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.list;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
public class NetworkUtils {
|
||||
|
||||
private static final Logger LOG = getLogger(NetworkUtils.class.getName());
|
||||
|
||||
public static List<NetworkInterface> getNetworkInterfaces() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> ifaces =
|
||||
NetworkInterface.getNetworkInterfaces();
|
||||
// Despite what the docs say, the return value can be null
|
||||
//noinspection ConstantConditions
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ dependencyVerification {
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
|
||||
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
|
||||
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
|
||||
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
|
||||
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
|
||||
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
|
||||
@@ -21,7 +21,7 @@ dependencyVerification {
|
||||
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
|
||||
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
||||
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
|
||||
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
|
||||
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
||||
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ apply from: '../dagger.gradle'
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-api', configuration: 'default')
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||
@@ -32,14 +32,6 @@ dependencies {
|
||||
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
|
||||
}
|
||||
|
||||
animalsniffer {
|
||||
// Allow requireNonNull: Android desugaring rewrites it (so it's safe for us to use),
|
||||
// and it gets used when passing method references instead of lambdas with Java 11.
|
||||
// Note that this line allows *all* methods from java.util.Objects.
|
||||
// That's the best that we can do with the configuration options that Animal Sniffer offers.
|
||||
ignore 'java.util.Objects'
|
||||
}
|
||||
|
||||
// needed to make test output available to bramble-java
|
||||
configurations {
|
||||
testOutput.extendsFrom(testCompile)
|
||||
|
||||
@@ -4,9 +4,6 @@ import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
@@ -23,6 +20,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.spongycastle.crypto.CryptoException;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
import org.whispersystems.curve25519.Curve25519;
|
||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.BasicAgreement;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.DerivationFunction;
|
||||
import org.bouncycastle.crypto.KeyEncoder;
|
||||
import org.bouncycastle.crypto.Mac;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.engines.AESLightEngine;
|
||||
import org.bouncycastle.crypto.engines.IESEngine;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.bouncycastle.crypto.parsers.ECIESPublicKeyParser;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.spongycastle.crypto.BasicAgreement;
|
||||
import org.spongycastle.crypto.BlockCipher;
|
||||
import org.spongycastle.crypto.CipherParameters;
|
||||
import org.spongycastle.crypto.CryptoException;
|
||||
import org.spongycastle.crypto.DerivationFunction;
|
||||
import org.spongycastle.crypto.KeyEncoder;
|
||||
import org.spongycastle.crypto.Mac;
|
||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||
import org.spongycastle.crypto.engines.AESLightEngine;
|
||||
import org.spongycastle.crypto.engines.IESEngine;
|
||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator;
|
||||
import org.spongycastle.crypto.generators.KDF2BytesGenerator;
|
||||
import org.spongycastle.crypto.macs.HMac;
|
||||
import org.spongycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.spongycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.spongycastle.crypto.parsers.ECIESPublicKeyParser;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.generators.SCrypt;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.spongycastle.crypto.generators.SCrypt;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.math.ec.ECCurve;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
@@ -11,6 +9,8 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.engines.XSalsa20Engine;
|
||||
import org.bouncycastle.crypto.generators.Poly1305KeyGenerator;
|
||||
import org.bouncycastle.crypto.macs.Poly1305;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.DataLengthException;
|
||||
import org.spongycastle.crypto.engines.XSalsa20Engine;
|
||||
import org.spongycastle.crypto.generators.Poly1305KeyGenerator;
|
||||
import org.spongycastle.crypto.macs.Poly1305;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
|
||||
@@ -2331,7 +2331,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
rs.next();
|
||||
long total = rs.getLong(1);
|
||||
long total = rs.getInt(1);
|
||||
rs.close();
|
||||
ps.close();
|
||||
return total;
|
||||
|
||||
@@ -28,9 +28,11 @@ import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -51,7 +53,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@@ -96,6 +98,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
|
||||
/**
|
||||
* Returns true if connections to the given address can be attempted.
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
protected abstract boolean isConnectable(InterfaceAddress local,
|
||||
InetSocketAddress remote);
|
||||
|
||||
@@ -395,6 +398,17 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
|
||||
return addrs;
|
||||
}
|
||||
|
||||
private List<NetworkInterface> getNetworkInterfaces() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> ifaces =
|
||||
NetworkInterface.getNetworkInterfaces();
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
|
||||
@@ -4,10 +4,10 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA3Digest;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.briarproject.bramble.util.Base32;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.SHA3Digest;
|
||||
import org.spongycastle.util.encoders.Base64;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.rendezvous;
|
||||
|
||||
import org.bouncycastle.crypto.engines.Salsa20Engine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
|
||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMultiplierProducesSameResultsAsDefault() throws Exception {
|
||||
// Instantiate the default implementation of the curve
|
||||
X9ECParameters defaultX9Parameters =
|
||||
TeleTrusTNamedCurves.getByName("brainpoolp256r1");
|
||||
ECCurve defaultCurve = defaultX9Parameters.getCurve();
|
||||
ECPoint defaultG = defaultX9Parameters.getG();
|
||||
BigInteger defaultN = defaultX9Parameters.getN();
|
||||
BigInteger defaultH = defaultX9Parameters.getH();
|
||||
ECDomainParameters defaultParameters = new ECDomainParameters(
|
||||
defaultCurve, defaultG, defaultN, defaultH);
|
||||
// Instantiate an implementation using the Montgomery ladder multiplier
|
||||
ECDomainParameters montgomeryParameters =
|
||||
constantTime(defaultParameters);
|
||||
// Generate two key pairs with each set of parameters, using the same
|
||||
// deterministic PRNG for both sets of parameters
|
||||
byte[] seed = new byte[32];
|
||||
new SecureRandom().nextBytes(seed);
|
||||
// Montgomery ladder multiplier
|
||||
SecureRandom random = new PseudoSecureRandom(seed);
|
||||
ECKeyGenerationParameters montgomeryGeneratorParams =
|
||||
new ECKeyGenerationParameters(montgomeryParameters, random);
|
||||
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
|
||||
montgomeryGenerator.init(montgomeryGeneratorParams);
|
||||
AsymmetricCipherKeyPair montgomeryKeyPair1 =
|
||||
montgomeryGenerator.generateKeyPair();
|
||||
ECPrivateKeyParameters montgomeryPrivate1 =
|
||||
(ECPrivateKeyParameters) montgomeryKeyPair1.getPrivate();
|
||||
ECPublicKeyParameters montgomeryPublic1 =
|
||||
(ECPublicKeyParameters) montgomeryKeyPair1.getPublic();
|
||||
AsymmetricCipherKeyPair montgomeryKeyPair2 =
|
||||
montgomeryGenerator.generateKeyPair();
|
||||
ECPrivateKeyParameters montgomeryPrivate2 =
|
||||
(ECPrivateKeyParameters) montgomeryKeyPair2.getPrivate();
|
||||
ECPublicKeyParameters montgomeryPublic2 =
|
||||
(ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
|
||||
// Default multiplier
|
||||
random = new PseudoSecureRandom(seed);
|
||||
ECKeyGenerationParameters defaultGeneratorParams =
|
||||
new ECKeyGenerationParameters(defaultParameters, random);
|
||||
ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();
|
||||
defaultGenerator.init(defaultGeneratorParams);
|
||||
AsymmetricCipherKeyPair defaultKeyPair1 =
|
||||
defaultGenerator.generateKeyPair();
|
||||
ECPrivateKeyParameters defaultPrivate1 =
|
||||
(ECPrivateKeyParameters) defaultKeyPair1.getPrivate();
|
||||
ECPublicKeyParameters defaultPublic1 =
|
||||
(ECPublicKeyParameters) defaultKeyPair1.getPublic();
|
||||
AsymmetricCipherKeyPair defaultKeyPair2 =
|
||||
defaultGenerator.generateKeyPair();
|
||||
ECPrivateKeyParameters defaultPrivate2 =
|
||||
(ECPrivateKeyParameters) defaultKeyPair2.getPrivate();
|
||||
ECPublicKeyParameters defaultPublic2 =
|
||||
(ECPublicKeyParameters) defaultKeyPair2.getPublic();
|
||||
// The key pairs generated with both sets of parameters should be equal
|
||||
assertEquals(montgomeryPrivate1.getD(), defaultPrivate1.getD());
|
||||
assertEquals(montgomeryPublic1.getQ(), defaultPublic1.getQ());
|
||||
assertEquals(montgomeryPrivate2.getD(), defaultPrivate2.getD());
|
||||
assertEquals(montgomeryPublic2.getQ(), defaultPublic2.getQ());
|
||||
// OK, all of the above was just sanity checks - now for the test!
|
||||
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
|
||||
agreement.init(montgomeryPrivate1);
|
||||
BigInteger sharedSecretMontgomeryMontgomery =
|
||||
agreement.calculateAgreement(montgomeryPublic2);
|
||||
agreement.init(montgomeryPrivate1);
|
||||
BigInteger sharedSecretMontgomeryDefault =
|
||||
agreement.calculateAgreement(defaultPublic2);
|
||||
agreement.init(defaultPrivate1);
|
||||
BigInteger sharedSecretDefaultMontgomery =
|
||||
agreement.calculateAgreement(montgomeryPublic2);
|
||||
agreement.init(defaultPrivate1);
|
||||
BigInteger sharedSecretDefaultDefault =
|
||||
agreement.calculateAgreement(defaultPublic2);
|
||||
// Shared secrets calculated with different multipliers should be equal
|
||||
assertEquals(sharedSecretMontgomeryMontgomery,
|
||||
sharedSecretMontgomeryDefault);
|
||||
assertEquals(sharedSecretMontgomeryMontgomery,
|
||||
sharedSecretDefaultMontgomery);
|
||||
assertEquals(sharedSecretMontgomeryMontgomery,
|
||||
sharedSecretDefaultDefault);
|
||||
}
|
||||
|
||||
private static ECDomainParameters constantTime(ECDomainParameters in) {
|
||||
ECCurve curve = in.getCurve().configure().setMultiplier(
|
||||
new MontgomeryLadderMultiplier()).create();
|
||||
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
|
||||
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
|
||||
ECPoint g = curve.createPoint(x, y);
|
||||
return new ECDomainParameters(curve, g, in.getN(), in.getH());
|
||||
}
|
||||
}
|
||||
@@ -3,26 +3,30 @@ package org.briarproject.bramble.crypto;
|
||||
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
|
||||
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||
|
||||
import org.bouncycastle.asn1.sec.SECNamedCurves;
|
||||
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.BasicAgreement;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.DSADigestSigner;
|
||||
import org.bouncycastle.crypto.signers.DSAKCalculator;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
|
||||
import org.spongycastle.asn1.sec.SECNamedCurves;
|
||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.spongycastle.crypto.BasicAgreement;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
|
||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.spongycastle.crypto.params.ParametersWithRandom;
|
||||
import org.spongycastle.crypto.signers.DSADigestSigner;
|
||||
import org.spongycastle.crypto.signers.DSAKCalculator;
|
||||
import org.spongycastle.crypto.signers.ECDSASigner;
|
||||
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
||||
import org.whispersystems.curve25519.Curve25519;
|
||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Provider;
|
||||
@@ -51,12 +55,14 @@ public class EllipticCurvePerformanceTest {
|
||||
for (String name : SEC_NAMES) {
|
||||
ECDomainParameters params =
|
||||
convertParams(SECNamedCurves.getByName(name));
|
||||
runTest(name, params);
|
||||
runTest(name + " default", params);
|
||||
runTest(name + " constant", constantTime(params));
|
||||
}
|
||||
for (String name : BRAINPOOL_NAMES) {
|
||||
ECDomainParameters params =
|
||||
convertParams(TeleTrusTNamedCurves.getByName(name));
|
||||
runTest(name, params);
|
||||
runTest(name + " default", params);
|
||||
runTest(name + " constant", constantTime(params));
|
||||
}
|
||||
runCurve25519Test();
|
||||
runEd25519Test();
|
||||
@@ -187,4 +193,13 @@ public class EllipticCurvePerformanceTest {
|
||||
return new ECDomainParameters(in.getCurve(), in.getG(), in.getN(),
|
||||
in.getH());
|
||||
}
|
||||
|
||||
private static ECDomainParameters constantTime(ECDomainParameters in) {
|
||||
ECCurve curve = in.getCurve().configure().setMultiplier(
|
||||
new MontgomeryLadderMultiplier()).create();
|
||||
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
|
||||
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
|
||||
ECPoint g = curve.createPoint(x, y);
|
||||
return new ECDomainParameters(curve, g, in.getN(), in.getH());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.crypto.CryptoException;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.bouncycastle.crypto.engines.Salsa20Engine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ public class TestFeatureFlagModule {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableTransferData() {
|
||||
public boolean shouldEnableShareAppViaOfflineHotspot() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableShareAppViaOfflineHotspot() {
|
||||
public boolean shouldEnableTransferData() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ dependencyVerification {
|
||||
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
|
||||
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
|
||||
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
@@ -26,14 +27,13 @@ dependencyVerification {
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
|
||||
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
||||
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
|
||||
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
|
||||
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
|
||||
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
|
||||
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
|
||||
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
|
||||
@@ -48,8 +48,8 @@ dependencyVerification {
|
||||
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
|
||||
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
||||
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
|
||||
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
|
||||
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
||||
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
'org.whispersystems:curve25519-java:0.5.0:curve25519-java-0.5.0.jar:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,15 +9,16 @@ import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.net.NetworkInterface.getNetworkInterfaces;
|
||||
import static java.util.Collections.list;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -34,14 +35,19 @@ class JavaNetworkManager implements NetworkManager {
|
||||
public NetworkStatus getNetworkStatus() {
|
||||
boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false;
|
||||
try {
|
||||
for (NetworkInterface i : getNetworkInterfaces()) {
|
||||
if (i.isLoopback() || !i.isUp()) continue;
|
||||
for (InetAddress addr : list(i.getInetAddresses())) {
|
||||
connected = true;
|
||||
if (addr instanceof Inet4Address) {
|
||||
hasIpv4 = true;
|
||||
} else if (!addr.isMulticastAddress()) {
|
||||
hasIpv6Unicast = true;
|
||||
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
|
||||
if (interfaces == null) {
|
||||
LOG.info("No network interfaces");
|
||||
} else {
|
||||
for (NetworkInterface i : list(interfaces)) {
|
||||
if (i.isLoopback() || !i.isUp()) continue;
|
||||
for (InetAddress addr : list(i.getInetAddresses())) {
|
||||
connected = true;
|
||||
if (addr instanceof Inet4Address) {
|
||||
hasIpv4 = true;
|
||||
} else if (!addr.isMulticastAddress()) {
|
||||
hasIpv6Unicast = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10306
|
||||
versionName "1.3.6"
|
||||
versionCode 10305
|
||||
versionName "1.3.5"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -110,7 +110,7 @@ dependencies {
|
||||
|
||||
implementation 'info.guardianproject.panic:panic:1.0'
|
||||
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
||||
implementation 'de.hdodenhof:circleimageview:3.0.1'
|
||||
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24
|
||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:3.3.0'
|
||||
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
|
||||
import org.briarproject.bramble.BrambleAndroidModule;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.account.BriarAccountModule;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
||||
import org.briarproject.bramble.system.ClockModule;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.account.SignInTestCreateAccount;
|
||||
@@ -22,7 +21,6 @@ import dagger.Component;
|
||||
AttachmentModule.class,
|
||||
ClockModule.class,
|
||||
MediaModule.class,
|
||||
RemovableDriveModule.class,
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
|
||||
import org.briarproject.bramble.BrambleAndroidModule;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.account.BriarAccountModule;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
||||
import org.briarproject.bramble.system.ClockModule;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||
@@ -21,7 +20,6 @@ import dagger.Component;
|
||||
AttachmentModule.class,
|
||||
ClockModule.class,
|
||||
MediaModule.class,
|
||||
RemovableDriveModule.class,
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
|
||||
@@ -344,13 +344,7 @@
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.StartupFailureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:finishOnTaskLaunch="true"
|
||||
android:label="@string/startup_failed_activity_title"
|
||||
android:launchMode="singleInstance"
|
||||
android:process=":briar_startup_failure"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
android:label="@string/startup_failed_activity_title" />
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.settings.SettingsActivity"
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
<li id="troubleshooting_1">If you can't download the app, try it with a different web
|
||||
browser app.
|
||||
</li>
|
||||
<li id="troubleshooting_2">To install the downloaded app,
|
||||
you might need to allow your browser to install unknown apps.
|
||||
We recommend to undo that after successful installation.
|
||||
<li id="troubleshooting_2">Ensure that your browser is allowed to download apps directly by
|
||||
giving it the permission or enabling the installation of apps from "Unknown Sources" in
|
||||
system settings.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
@@ -157,8 +157,7 @@ public class AppModule {
|
||||
@Singleton
|
||||
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
|
||||
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
|
||||
AndroidRemovableDrivePluginFactory drive,
|
||||
FeatureFlags featureFlags) {
|
||||
AndroidRemovableDrivePluginFactory drive) {
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@@ -169,11 +168,7 @@ public class AppModule {
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
if (SDK_INT >= 19 && featureFlags.shouldEnableTransferData()) {
|
||||
return singletonList(drive);
|
||||
} else {
|
||||
return emptyList();
|
||||
}
|
||||
return SDK_INT >= 19 ? singletonList(drive) : emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
@@ -33,7 +34,11 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
import static android.content.Intent.ACTION_SHUTDOWN;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
@@ -50,6 +55,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
|
||||
@@ -60,6 +66,8 @@ public class BriarService extends Service {
|
||||
|
||||
public static String EXTRA_START_RESULT =
|
||||
"org.briarproject.briar.START_RESULT";
|
||||
public static String EXTRA_NOTIFICATION_ID =
|
||||
"org.briarproject.briar.FAILURE_NOTIFICATION_ID";
|
||||
public static String EXTRA_STARTUP_FAILED =
|
||||
"org.briarproject.briar.STARTUP_FAILED";
|
||||
|
||||
@@ -127,11 +135,12 @@ public class BriarService extends Service {
|
||||
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
ongoingChannel.setShowBadge(false);
|
||||
nm.createNotificationChannel(ongoingChannel);
|
||||
// Delete the unused channel previously used for startup
|
||||
// failure notifications
|
||||
// TODO: Remove this ID after a reasonable upgrade period
|
||||
// (added 2021-07-12)
|
||||
nm.deleteNotificationChannel(FAILURE_CHANNEL_ID);
|
||||
NotificationChannel failureChannel = new NotificationChannel(
|
||||
FAILURE_CHANNEL_ID,
|
||||
getString(R.string.startup_failed_notification_title),
|
||||
IMPORTANCE_DEFAULT);
|
||||
failureChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
nm.createNotificationChannel(failureChannel);
|
||||
}
|
||||
Notification foregroundNotification =
|
||||
notificationManager.getForegroundNotification();
|
||||
@@ -147,7 +156,7 @@ public class BriarService extends Service {
|
||||
} else {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Startup failed: " + result);
|
||||
showStartupFailure(result);
|
||||
showStartupFailureNotification(result);
|
||||
stopSelf();
|
||||
}
|
||||
}, "LifecycleStartup");
|
||||
@@ -173,13 +182,29 @@ public class BriarService extends Service {
|
||||
Localizer.getInstance().setLocale(this);
|
||||
}
|
||||
|
||||
private void showStartupFailure(StartResult result) {
|
||||
private void showStartupFailureNotification(StartResult result) {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
// Bring the entry activity to the front to clear the back stack
|
||||
Intent i = new Intent(BriarService.this, ENTRY_ACTIVITY);
|
||||
NotificationCompat.Builder b = new NotificationCompat.Builder(
|
||||
BriarService.this, FAILURE_CHANNEL_ID);
|
||||
b.setSmallIcon(android.R.drawable.stat_notify_error);
|
||||
b.setContentTitle(getText(
|
||||
R.string.startup_failed_notification_title));
|
||||
b.setContentText(getText(
|
||||
R.string.startup_failed_notification_text));
|
||||
Intent i = new Intent(BriarService.this,
|
||||
StartupFailureActivity.class);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
i.putExtra(EXTRA_START_RESULT, result);
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, FAILURE_NOTIFICATION_ID);
|
||||
b.setContentIntent(PendingIntent.getActivity(BriarService.this,
|
||||
0, i, FLAG_UPDATE_CURRENT));
|
||||
NotificationManager nm = (NotificationManager)
|
||||
requireNonNull(getSystemService(NOTIFICATION_SERVICE));
|
||||
nm.notify(FAILURE_NOTIFICATION_ID, b.build());
|
||||
// Bring the dashboard to the front to clear the back stack
|
||||
i = new Intent(BriarService.this, ENTRY_ACTIVITY);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i.putExtra(EXTRA_STARTUP_FAILED, true);
|
||||
i.putExtra(EXTRA_START_RESULT, result);
|
||||
startActivity(i);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -14,7 +15,9 @@ import org.briarproject.briar.android.fragment.ErrorFragment;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -38,6 +41,14 @@ public class StartupFailureActivity extends BaseActivity implements
|
||||
private void handleIntent(Intent i) {
|
||||
StartResult result =
|
||||
(StartResult) i.getSerializableExtra(EXTRA_START_RESULT);
|
||||
int notificationId = i.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
|
||||
|
||||
// cancel notification
|
||||
if (notificationId > -1) {
|
||||
Object o = getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) requireNonNull(o);
|
||||
nm.cancel(notificationId);
|
||||
}
|
||||
|
||||
// show proper error message
|
||||
int errorRes;
|
||||
@@ -67,4 +78,5 @@ public class StartupFailureActivity extends BaseActivity implements
|
||||
public void runOnDbThread(@NonNull Runnable runnable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
@@ -50,7 +50,7 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
|
||||
return text;
|
||||
}
|
||||
|
||||
boolean isRssFeed() {
|
||||
public boolean isRssFeed() {
|
||||
return header.isRssFeed();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.annotation.UiThread;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
@@ -45,7 +44,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView text;
|
||||
private final ViewGroup commentContainer;
|
||||
private final boolean fullText, authorClickable;
|
||||
private final int padding;
|
||||
|
||||
private final OnBlogPostClickListener listener;
|
||||
|
||||
@@ -63,8 +61,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
reblogButton = v.findViewById(R.id.commentView);
|
||||
text = v.findViewById(R.id.textView);
|
||||
commentContainer = v.findViewById(R.id.commentContainer);
|
||||
padding = ctx.getResources()
|
||||
.getDimensionPixelSize(R.dimen.listitem_vertical_margin);
|
||||
}
|
||||
|
||||
void hideReblogButton() {
|
||||
@@ -133,12 +129,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
} else {
|
||||
reblogger.setVisibility(GONE);
|
||||
}
|
||||
|
||||
// Apply Android 4 padding fix after setting up author/reblogger views
|
||||
if (SDK_INT < 21) {
|
||||
reblogger.setPadding(padding, padding, padding, padding);
|
||||
author.setPadding(padding, padding, padding, padding);
|
||||
}
|
||||
}
|
||||
|
||||
private void onBindComment(BlogCommentItem item, boolean authorClickable) {
|
||||
|
||||
@@ -57,12 +57,8 @@ public class RssFeedActivity extends BriarActivity
|
||||
onBackPressed();
|
||||
}
|
||||
} else if (result == FAILED) {
|
||||
String url = viewModel.getUrlFailedImport();
|
||||
if (url == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
RssFeedImportFailedDialogFragment dialog =
|
||||
RssFeedImportFailedDialogFragment.newInstance(url);
|
||||
RssFeedImportFailedDialogFragment.newInstance();
|
||||
dialog.show(getSupportFragmentManager(),
|
||||
RssFeedImportFailedDialogFragment.TAG);
|
||||
} else if (result == EXISTS) {
|
||||
|
||||
@@ -25,15 +25,8 @@ public class RssFeedImportFailedDialogFragment extends DialogFragment {
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private RssFeedViewModel viewModel;
|
||||
|
||||
private static final String ARG_URL = "url";
|
||||
|
||||
static RssFeedImportFailedDialogFragment newInstance(String retryUrl) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_URL, retryUrl);
|
||||
RssFeedImportFailedDialogFragment f =
|
||||
new RssFeedImportFailedDialogFragment();
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
static RssFeedImportFailedDialogFragment newInstance() {
|
||||
return new RssFeedImportFailedDialogFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,8 +45,8 @@ public class RssFeedImportFailedDialogFragment extends DialogFragment {
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setMessage(R.string.blogs_rss_feeds_import_error);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.try_again_button, (dialog, which) ->
|
||||
viewModel.importFeed(requireArguments().getString(ARG_URL)));
|
||||
builder.setPositiveButton(R.string.try_again_button,
|
||||
(dialog, which) -> viewModel.retryImportFeed());
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.briarproject.briar.api.feed.Feed;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
|
||||
@@ -159,9 +159,11 @@ class RssFeedViewModel extends DbViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getUrlFailedImport() {
|
||||
return urlFailedImport;
|
||||
void retryImportFeed() {
|
||||
if (urlFailedImport == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
importFeed(urlFailedImport);
|
||||
}
|
||||
|
||||
private boolean exists(String url) {
|
||||
|
||||
@@ -59,7 +59,7 @@ public class LinkExchangeFragment extends BaseFragment
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
viewModel = new ViewModelProvider(getActivity(), viewModelFactory)
|
||||
.get(AddContactViewModel.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -21,7 +22,6 @@ import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.widget.ImageViewCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import static android.view.View.FOCUS_DOWN;
|
||||
@@ -58,7 +58,7 @@ public class FinalFragment extends Fragment {
|
||||
return f;
|
||||
}
|
||||
|
||||
private NestedScrollView scrollView;
|
||||
private ScrollView scrollView;
|
||||
protected Button buttonView;
|
||||
|
||||
@Nullable
|
||||
@@ -69,7 +69,7 @@ public class FinalFragment extends Fragment {
|
||||
View v = inflater
|
||||
.inflate(R.layout.fragment_final, container, false);
|
||||
|
||||
scrollView = (NestedScrollView) v;
|
||||
scrollView = (ScrollView) v;
|
||||
ImageView iconView = v.findViewById(R.id.iconView);
|
||||
TextView titleView = v.findViewById(R.id.titleView);
|
||||
TextView textView = v.findViewById(R.id.textView);
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.wifi.WifiManager;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
|
||||
/**
|
||||
* Abstract base class for the ConditionManagers that ensure that the conditions
|
||||
* to open a hotspot are fulfilled. There are different extensions of this for
|
||||
* API levels lower than 29 and 29+.
|
||||
*/
|
||||
abstract class AbstractConditionManager {
|
||||
|
||||
enum Permission {
|
||||
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||
}
|
||||
|
||||
protected final Consumer<Boolean> permissionUpdateCallback;
|
||||
protected FragmentActivity ctx;
|
||||
WifiManager wifiManager;
|
||||
|
||||
AbstractConditionManager(Consumer<Boolean> permissionUpdateCallback) {
|
||||
this.permissionUpdateCallback = permissionUpdateCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a FragmentActivity context here during `onCreateView()`.
|
||||
*/
|
||||
void init(FragmentActivity ctx) {
|
||||
this.ctx = ctx;
|
||||
this.wifiManager = (WifiManager) ctx.getApplicationContext()
|
||||
.getSystemService(WIFI_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this during onStart() in the fragment where the ConditionManager
|
||||
* is used.
|
||||
*/
|
||||
abstract void onStart();
|
||||
|
||||
/**
|
||||
* Check if all required conditions are met such that the hotspot can be
|
||||
* started. If any precondition is not met yet, bring up relevant dialogs
|
||||
* asking the user to grant relevant permissions or take relevant actions.
|
||||
*
|
||||
* @return true if conditions are fulfilled and flow can continue.
|
||||
*/
|
||||
abstract boolean checkAndRequestConditions();
|
||||
|
||||
void showDenialDialog(FragmentActivity ctx,
|
||||
@StringRes int title, @StringRes int body,
|
||||
DialogInterface.OnClickListener onOkClicked, Runnable onDismiss) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setPositiveButton(R.string.ok, onOkClicked);
|
||||
builder.setNegativeButton(R.string.cancel,
|
||||
(dialog, which) -> ctx.supportFinishAfterTransition());
|
||||
builder.setOnDismissListener(dialog -> onDismiss.run());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
void showRationale(Context ctx, @StringRes int title,
|
||||
@StringRes int body, Runnable onContinueClicked,
|
||||
Runnable onDismiss) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
(dialog, which) -> onContinueClicked.run());
|
||||
builder.setOnDismissListener(dialog -> onDismiss.run());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
@@ -44,7 +43,6 @@ public abstract class AbstractTabsFragment extends Fragment {
|
||||
|
||||
protected Button stopButton;
|
||||
protected Button connectedButton;
|
||||
protected TextView connectedView;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
@@ -87,9 +85,6 @@ public abstract class AbstractTabsFragment extends Fragment {
|
||||
finishAfterTransition(requireActivity());
|
||||
});
|
||||
connectedButton = view.findViewById(R.id.connectedButton);
|
||||
connectedView = view.findViewById(R.id.connectedView);
|
||||
viewModel.getPeersConnectedEvent()
|
||||
.observe(getViewLifecycleOwner(), this::onPeerConnected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,13 +126,4 @@ public abstract class AbstractTabsFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void onPeerConnected(int peers) {
|
||||
if (peers == 0) {
|
||||
connectedView.setText(R.string.hotspot_no_peers_connected);
|
||||
} else {
|
||||
connectedView.setText(getResources().getQuantityString(
|
||||
R.plurals.hotspot_peers_connected, peers, peers));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,83 +1,171 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
|
||||
|
||||
/**
|
||||
* This class ensures that the conditions to open a hotspot are fulfilled on
|
||||
* API levels < 29.
|
||||
* This class ensures that the conditions to open a hotspot are fulfilled.
|
||||
* <p>
|
||||
* Be sure to call {@link #onRequestPermissionResult(Boolean)} and
|
||||
* {@link #onRequestWifiEnabledResult()} when you get the
|
||||
* {@link ActivityResult}.
|
||||
* <p>
|
||||
* As soon as {@link #checkAndRequestConditions()} returns true,
|
||||
* all conditions are fulfilled.
|
||||
*/
|
||||
class ConditionManager extends AbstractConditionManager {
|
||||
@NotNullByDefault
|
||||
class ConditionManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ConditionManager.class.getName());
|
||||
private enum Permission {
|
||||
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||
}
|
||||
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
private Permission wifiSetting = Permission.SHOW_RATIONALE;
|
||||
|
||||
private final FragmentActivity ctx;
|
||||
private final WifiManager wifiManager;
|
||||
private final ActivityResultLauncher<String> locationRequest;
|
||||
private final ActivityResultLauncher<Intent> wifiRequest;
|
||||
|
||||
ConditionManager(ActivityResultCaller arc,
|
||||
Consumer<Boolean> permissionUpdateCallback) {
|
||||
super(permissionUpdateCallback);
|
||||
wifiRequest = arc.registerForActivityResult(
|
||||
new StartActivityForResult(),
|
||||
result -> permissionUpdateCallback
|
||||
.accept(wifiManager.isWifiEnabled()));
|
||||
ConditionManager(FragmentActivity ctx,
|
||||
ActivityResultLauncher<String> locationRequest,
|
||||
ActivityResultLauncher<Intent> wifiRequest) {
|
||||
this.ctx = ctx;
|
||||
this.wifiManager = (WifiManager) ctx.getApplicationContext()
|
||||
.getSystemService(WIFI_SERVICE);
|
||||
this.locationRequest = locationRequest;
|
||||
this.wifiRequest = wifiRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStart() {
|
||||
// nothing to do here
|
||||
/**
|
||||
* Call this to reset state when UI starts,
|
||||
* because state might have changed.
|
||||
*/
|
||||
void resetPermissions() {
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
wifiSetting = Permission.SHOW_RATIONALE;
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(String.format("areEssentialPermissionsGranted(): " +
|
||||
"wifiManager.isWifiEnabled()? %b",
|
||||
wifiManager.isWifiEnabled()));
|
||||
}
|
||||
return wifiManager.isWifiEnabled();
|
||||
/**
|
||||
* This makes a request for location permission.
|
||||
* If {@link #checkAndRequestConditions()} returns true, you can continue.
|
||||
*/
|
||||
void startConditionChecks() {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* @return true if conditions are fulfilled and flow can continue.
|
||||
*/
|
||||
boolean checkAndRequestConditions() {
|
||||
if (areEssentialPermissionsGranted()) return true;
|
||||
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
// Try enabling the Wifi and return true if that seems to have been
|
||||
// successful, i.e. "Wifi is either already in the requested state, or
|
||||
// in progress toward the requested state".
|
||||
if (wifiManager.setWifiEnabled(true)) {
|
||||
LOG.info("Enabled wifi");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wifi is not enabled and we can't seem to enable it, so ask the user
|
||||
// to enable it for us.
|
||||
showRationale(ctx, R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_enable_body,
|
||||
this::requestEnableWiFi,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
// If an essential permission has been permanently denied, ask the
|
||||
// user to change the setting
|
||||
if (locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||
showDenialDialog(R.string.permission_location_title,
|
||||
R.string.permission_hotspot_location_denied_body,
|
||||
getGoToSettingsListener(ctx));
|
||||
return false;
|
||||
}
|
||||
if (wifiSetting == Permission.PERMANENTLY_DENIED) {
|
||||
showDenialDialog(R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_denied_body,
|
||||
(d, w) -> requestEnableWiFi());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we show the rationale for location permission or Wi-Fi?
|
||||
if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(R.string.permission_location_title,
|
||||
R.string.permission_hotspot_location_request_body,
|
||||
this::requestPermissions);
|
||||
} else if (wifiSetting == Permission.SHOW_RATIONALE) {
|
||||
showRationale(R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_enable_body,
|
||||
this::requestEnableWiFi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onRequestPermissionResult(@Nullable Boolean granted) {
|
||||
if (granted != null && granted) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(ctx,
|
||||
ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
void onRequestWifiEnabledResult() {
|
||||
wifiSetting = wifiManager.isWifiEnabled() ? Permission.GRANTED :
|
||||
Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
if (SDK_INT < 29) {
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
//noinspection deprecation
|
||||
return wifiManager.setWifiEnabled(true);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return locationPermission == Permission.GRANTED
|
||||
&& wifiManager.isWifiEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
private void showDenialDialog(@StringRes int title, @StringRes int body,
|
||||
OnClickListener onOkClicked) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setPositiveButton(R.string.ok, onOkClicked);
|
||||
builder.setNegativeButton(R.string.cancel,
|
||||
(dialog, which) -> ctx.supportFinishAfterTransition());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showRationale(@StringRes int title, @StringRes int body,
|
||||
Runnable onContinueClicked) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
(dialog, which) -> onContinueClicked.run());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
private void requestEnableWiFi() {
|
||||
wifiRequest.launch(new Intent(Settings.ACTION_WIFI_SETTINGS));
|
||||
Intent i = SDK_INT < 29 ?
|
||||
new Intent(Settings.ACTION_WIFI_SETTINGS) :
|
||||
new Intent(Settings.Panel.ACTION_WIFI);
|
||||
wifiRequest.launch(i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
|
||||
|
||||
/**
|
||||
* This class ensures that the conditions to open a hotspot are fulfilled on
|
||||
* API levels >= 29.
|
||||
* <p>
|
||||
* As soon as {@link #checkAndRequestConditions()} returns true,
|
||||
* all conditions are fulfilled.
|
||||
*/
|
||||
@RequiresApi(29)
|
||||
class ConditionManager29 extends AbstractConditionManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ConditionManager29.class.getName());
|
||||
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
|
||||
private final ActivityResultLauncher<String> locationRequest;
|
||||
private final ActivityResultLauncher<Intent> wifiRequest;
|
||||
|
||||
ConditionManager29(ActivityResultCaller arc,
|
||||
Consumer<Boolean> permissionUpdateCallback) {
|
||||
super(permissionUpdateCallback);
|
||||
locationRequest = arc.registerForActivityResult(
|
||||
new RequestPermission(), granted -> {
|
||||
onRequestPermissionResult(granted);
|
||||
permissionUpdateCallback.accept(TRUE.equals(granted));
|
||||
});
|
||||
wifiRequest = arc.registerForActivityResult(
|
||||
new StartActivityForResult(),
|
||||
result -> permissionUpdateCallback
|
||||
.accept(wifiManager.isWifiEnabled())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStart() {
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
boolean isWifiEnabled = wifiManager.isWifiEnabled();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(String.format("areEssentialPermissionsGranted(): " +
|
||||
"locationPermission? %s, " +
|
||||
"wifiManager.isWifiEnabled()? %b",
|
||||
locationPermission, isWifiEnabled));
|
||||
}
|
||||
return locationPermission == Permission.GRANTED && isWifiEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean checkAndRequestConditions() {
|
||||
if (areEssentialPermissionsGranted()) return true;
|
||||
|
||||
if (locationPermission == Permission.UNKNOWN) {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the location permission has been permanently denied, ask the
|
||||
// user to change the setting
|
||||
if (locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||
showDenialDialog(ctx, R.string.permission_location_title,
|
||||
R.string.permission_hotspot_location_denied_body,
|
||||
getGoToSettingsListener(ctx),
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we show the rationale for location permission?
|
||||
if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(ctx, R.string.permission_location_title,
|
||||
R.string.permission_hotspot_location_request_body,
|
||||
this::requestPermissions,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If Wifi is not enabled, we show the rationale for enabling Wifi?
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
showRationale(ctx, R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_enable_body,
|
||||
this::requestEnableWiFi,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// we shouldn't usually reach this point, but if we do, return false
|
||||
// anyway to force a recheck. Maybe some condition changed in the
|
||||
// meantime.
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onRequestPermissionResult(@Nullable Boolean granted) {
|
||||
if (granted != null && granted) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(ctx,
|
||||
ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
private void requestEnableWiFi() {
|
||||
wifiRequest.launch(new Intent(Settings.Panel.ACTION_WIFI));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.util.ActivityLaunchers.CreateDocumentAdvanced;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -23,6 +22,7 @@ import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@@ -33,10 +33,12 @@ import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static androidx.activity.result.contract.ActivityResultContracts.CreateDocument;
|
||||
import static androidx.transition.TransitionManager.beginDelayedTransition;
|
||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||
import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName;
|
||||
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class FallbackFragment extends BaseFragment {
|
||||
@@ -48,7 +50,7 @@ public class FallbackFragment extends BaseFragment {
|
||||
|
||||
private HotspotViewModel viewModel;
|
||||
private final ActivityResultLauncher<String> launcher =
|
||||
registerForActivityResult(new CreateDocumentAdvanced(),
|
||||
registerForActivityResult(new CreateDocument(),
|
||||
this::onDocumentCreated);
|
||||
private Button fallbackButton;
|
||||
private ProgressBar progressBar;
|
||||
@@ -73,7 +75,7 @@ public class FallbackFragment extends BaseFragment {
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater
|
||||
.inflate(R.layout.fragment_hotspot_fallback, container, false);
|
||||
.inflate(R.layout.fragment_hotspot_save_apk, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,7 +92,8 @@ public class FallbackFragment extends BaseFragment {
|
||||
if (SDK_INT >= 19) launcher.launch(getApkFileName());
|
||||
else viewModel.exportApk();
|
||||
});
|
||||
viewModel.getSavedApkToUri().observeEvent(this, this::shareUri);
|
||||
viewModel.getSavedApkToUri()
|
||||
.observeEvent(this, uri -> shareUri(this, uri));
|
||||
}
|
||||
|
||||
private void onDocumentCreated(@Nullable Uri uri) {
|
||||
@@ -104,12 +107,12 @@ public class FallbackFragment extends BaseFragment {
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
void shareUri(Uri uri) {
|
||||
static void shareUri(Fragment fragment, Uri uri) {
|
||||
Intent i = new Intent(ACTION_SEND);
|
||||
i.putExtra(EXTRA_STREAM, uri);
|
||||
i.setType("*/*"); // gives us all sharing options
|
||||
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||
Context ctx = requireContext();
|
||||
Context ctx = fragment.requireContext();
|
||||
if (SDK_INT <= 19) {
|
||||
// Workaround for Android bug:
|
||||
// ctx.grantUriPermission also needed for Android 4
|
||||
@@ -121,7 +124,7 @@ public class FallbackFragment extends BaseFragment {
|
||||
FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
startActivity(Intent.createChooser(i, null));
|
||||
fragment.startActivity(Intent.createChooser(i, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,13 +59,12 @@ public class HotspotActivity extends BriarActivity
|
||||
// check if fragment is already added
|
||||
// to not lose state on configuration changes
|
||||
if (fm.findFragmentByTag(tag) == null) {
|
||||
if (started.wasNotYetConsumed()) {
|
||||
started.consume();
|
||||
if (!started.consume()) {
|
||||
showFragment(fm, new HotspotFragment(), tag);
|
||||
}
|
||||
}
|
||||
} else if (hotspotState instanceof HotspotError) {
|
||||
HotspotError error = (HotspotError) hotspotState;
|
||||
HotspotError error = ((HotspotError) hotspotState);
|
||||
showErrorFragment(error.getError());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,8 +3,12 @@ package org.briarproject.briar.android.hotspot;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -21,6 +25,8 @@ public class HotspotFragment extends AbstractTabsFragment {
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
connectedButton.setOnClickListener(v -> showNextFragment());
|
||||
viewModel.getPeerConnectedEvent().observeEvent(getViewLifecycleOwner(),
|
||||
this::onPeerConnected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,6 +39,17 @@ public class HotspotFragment extends AbstractTabsFragment {
|
||||
return QrHotspotFragment.newInstance(true);
|
||||
}
|
||||
|
||||
private void onPeerConnected(boolean connected) {
|
||||
if (!connected) return;
|
||||
new BriarSnackbarBuilder()
|
||||
.setAction(R.string.hotspot_peer_connected_action, v ->
|
||||
showNextFragment())
|
||||
.make(connectedButton, R.string.hotspot_peer_connected,
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(connectedButton)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showNextFragment() {
|
||||
Fragment f = new WebsiteFragment();
|
||||
String tag = WebsiteFragment.TAG;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
@@ -9,7 +10,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
@@ -20,14 +20,15 @@ import org.briarproject.briar.R;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.content.pm.ApplicationInfo.FLAG_TEST_ONLY;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.FOCUS_DOWN;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static androidx.transition.TransitionManager.beginDelayedTransition;
|
||||
@@ -44,15 +45,22 @@ public class HotspotIntroFragment extends Fragment {
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private HotspotViewModel viewModel;
|
||||
private ConditionManager conditionManager;
|
||||
|
||||
private Button startButton;
|
||||
private ProgressBar progressBar;
|
||||
private TextView progressTextView;
|
||||
private ScrollView scrollView;
|
||||
|
||||
private final AbstractConditionManager conditionManager = SDK_INT < 29 ?
|
||||
new ConditionManager(this, this::onPermissionUpdate) :
|
||||
new ConditionManager29(this, this::onPermissionUpdate);
|
||||
private final ActivityResultLauncher<String> locationRequest =
|
||||
registerForActivityResult(new RequestPermission(), granted -> {
|
||||
conditionManager.onRequestPermissionResult(granted);
|
||||
startHotspot();
|
||||
});
|
||||
private final ActivityResultLauncher<Intent> wifiRequest =
|
||||
registerForActivityResult(new StartActivityForResult(), result -> {
|
||||
conditionManager.onRequestWifiEnabledResult();
|
||||
startHotspot();
|
||||
});
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
@@ -61,6 +69,8 @@ public class HotspotIntroFragment extends Fragment {
|
||||
getAndroidComponent(activity).inject(this);
|
||||
viewModel = new ViewModelProvider(activity, viewModelFactory)
|
||||
.get(HotspotViewModel.class);
|
||||
conditionManager =
|
||||
new ConditionManager(activity, locationRequest, wifiRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,11 +83,11 @@ public class HotspotIntroFragment extends Fragment {
|
||||
startButton = v.findViewById(R.id.startButton);
|
||||
progressBar = v.findViewById(R.id.progressBar);
|
||||
progressTextView = v.findViewById(R.id.progressTextView);
|
||||
scrollView = v.findViewById(R.id.scrollView);
|
||||
|
||||
startButton.setOnClickListener(this::onButtonClick);
|
||||
|
||||
conditionManager.init(requireActivity());
|
||||
startButton.setOnClickListener(button -> {
|
||||
startButton.setEnabled(false);
|
||||
conditionManager.startConditionChecks();
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
@@ -85,17 +95,11 @@ public class HotspotIntroFragment extends Fragment {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
conditionManager.onStart();
|
||||
// Scroll down in case the screen is small, so the button is visible
|
||||
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
|
||||
conditionManager.resetPermissions();
|
||||
}
|
||||
|
||||
private void onButtonClick(View view) {
|
||||
startButton.setEnabled(false);
|
||||
startHotspotIfConditionsFulfilled();
|
||||
}
|
||||
|
||||
private void startHotspotIfConditionsFulfilled() {
|
||||
private void startHotspot() {
|
||||
startButton.setEnabled(true);
|
||||
if (conditionManager.checkAndRequestConditions()) {
|
||||
showInstallWarningIfNeeded();
|
||||
beginDelayedTransition((ViewGroup) requireView());
|
||||
@@ -106,13 +110,6 @@ public class HotspotIntroFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void onPermissionUpdate(boolean recheckPermissions) {
|
||||
startButton.setEnabled(true);
|
||||
if (recheckPermissions) {
|
||||
startHotspotIfConditionsFulfilled();
|
||||
}
|
||||
}
|
||||
|
||||
private void showInstallWarningIfNeeded() {
|
||||
Context ctx = requireContext();
|
||||
ApplicationInfo applicationInfo;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -11,7 +10,6 @@ import android.net.wifi.p2p.WifiP2pManager;
|
||||
import android.net.wifi.p2p.WifiP2pManager.ActionListener;
|
||||
import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
@@ -36,7 +34,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import static android.content.Context.POWER_SERVICE;
|
||||
import static android.content.Context.WIFI_P2P_SERVICE;
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
|
||||
@@ -47,34 +44,30 @@ import static android.net.wifi.p2p.WifiP2pManager.ERROR;
|
||||
import static android.net.wifi.p2p.WifiP2pManager.NO_SERVICE_REQUESTS;
|
||||
import static android.net.wifi.p2p.WifiP2pManager.P2P_UNSUPPORTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.PowerManager.FULL_WAKE_LOCK;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
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.briar.android.util.UiUtils.handleException;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class HotspotManager {
|
||||
class HotspotManager implements ActionListener {
|
||||
|
||||
interface HotspotListener {
|
||||
@UiThread
|
||||
void onStartingHotspot();
|
||||
|
||||
@IoExecutor
|
||||
void onHotspotStarted(NetworkConfig networkConfig);
|
||||
|
||||
@UiThread
|
||||
void onPeersUpdated(int peers);
|
||||
void onDeviceConnected();
|
||||
|
||||
void onHotspotStopped();
|
||||
|
||||
@UiThread
|
||||
void onHotspotError(String error);
|
||||
}
|
||||
|
||||
private static final Logger LOG = getLogger(HotspotManager.class.getName());
|
||||
|
||||
private static final int MAX_FRAMEWORK_ATTEMPTS = 5;
|
||||
private static final int MAX_GROUP_INFO_ATTEMPTS = 5;
|
||||
private static final int RETRY_DELAY_MILLIS = 1000;
|
||||
private static final String HOTSPOT_NAMESPACE = "hotspot";
|
||||
@@ -91,17 +84,14 @@ class HotspotManager {
|
||||
private final SecureRandom random;
|
||||
private final WifiManager wifiManager;
|
||||
private final WifiP2pManager wifiP2pManager;
|
||||
private final PowerManager powerManager;
|
||||
private final Handler handler;
|
||||
private final String lockTag;
|
||||
|
||||
private HotspotListener listener;
|
||||
private WifiManager.WifiLock wifiLock;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private WifiP2pManager.Channel channel;
|
||||
@Nullable
|
||||
@RequiresApi(29)
|
||||
private volatile NetworkConfig savedNetworkConfig = null;
|
||||
private volatile NetworkConfig savedNetworkConfig;
|
||||
|
||||
@Inject
|
||||
HotspotManager(Application ctx,
|
||||
@@ -120,7 +110,6 @@ class HotspotManager {
|
||||
.getSystemService(WIFI_SERVICE);
|
||||
wifiP2pManager =
|
||||
(WifiP2pManager) ctx.getSystemService(WIFI_P2P_SERVICE);
|
||||
powerManager = (PowerManager) ctx.getSystemService(POWER_SERVICE);
|
||||
handler = new Handler(ctx.getMainLooper());
|
||||
lockTag = ctx.getPackageName() + ":app-sharing-hotspot";
|
||||
}
|
||||
@@ -137,106 +126,30 @@ class HotspotManager {
|
||||
return;
|
||||
}
|
||||
listener.onStartingHotspot();
|
||||
acquireLocks();
|
||||
startWifiP2pFramework(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* As soon as Wifi is enabled, we try starting the WifiP2p framework.
|
||||
* If Wifi has just been enabled, it is possible that will fail. If that
|
||||
* happens we try again for MAX_FRAMEWORK_ATTEMPTS times after a delay of
|
||||
* RETRY_DELAY_MILLIS after each attempt.
|
||||
* <p>
|
||||
* Rationale: it can take a few milliseconds for WifiP2p to become available
|
||||
* after enabling Wifi. Depending on the API level it is possible to check this
|
||||
* using {@link WifiP2pManager#requestP2pState} or register a BroadcastReceiver
|
||||
* on the WIFI_P2P_STATE_CHANGED_ACTION to get notified when WifiP2p is really
|
||||
* available. Trying to implement a solution that works reliably using these
|
||||
* checks turned out to be a long rabbit-hole with lots of corner cases and
|
||||
* workarounds for specific situations.
|
||||
* Instead we now rely on this trial-and-error approach of just starting
|
||||
* the framework and retrying if it fails.
|
||||
* <p>
|
||||
* We'll realize that the framework is busy when the ActionListener passed
|
||||
* to {@link WifiP2pManager#createGroup} is called with onFailure(BUSY)
|
||||
*/
|
||||
@UiThread
|
||||
private void startWifiP2pFramework(int attempt) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("startWifiP2pFramework attempt: " + attempt);
|
||||
}
|
||||
/*
|
||||
* It is important that we call WifiP2pManager#initialize again
|
||||
* for every attempt to starting the framework because otherwise,
|
||||
* createGroup() will continue to fail with a BUSY state.
|
||||
*/
|
||||
channel = wifiP2pManager.initialize(ctx, ctx.getMainLooper(), null);
|
||||
if (channel == null) {
|
||||
releaseHotspotWithError(
|
||||
listener.onHotspotError(
|
||||
ctx.getString(R.string.hotspot_error_no_wifi_direct));
|
||||
return;
|
||||
}
|
||||
|
||||
ActionListener listener = new ActionListener() {
|
||||
@Override
|
||||
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
|
||||
public void onSuccess() {
|
||||
requestGroupInfo(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
|
||||
public void onFailure(int reason) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("onFailure: " + reason);
|
||||
}
|
||||
if (reason == BUSY) {
|
||||
// WifiP2p not ready yet or hotspot already running
|
||||
restartWifiP2pFramework(attempt);
|
||||
} else if (reason == P2P_UNSUPPORTED) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"p2p unsupported"));
|
||||
} else if (reason == ERROR) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"p2p error"));
|
||||
} else if (reason == NO_SERVICE_REQUESTS) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"no service requests"));
|
||||
} else {
|
||||
// all cases covered, in doubt set to error
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed_unknown,
|
||||
reason));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (SDK_INT >= 29) {
|
||||
Runnable createGroup = () -> {
|
||||
NetworkConfig c = requireNonNull(savedNetworkConfig);
|
||||
WifiP2pConfig config = new WifiP2pConfig.Builder()
|
||||
.setGroupOperatingBand(GROUP_OWNER_BAND_2GHZ)
|
||||
.setNetworkName(c.ssid)
|
||||
.setPassphrase(c.password)
|
||||
.build();
|
||||
wifiP2pManager.createGroup(channel, config, listener);
|
||||
};
|
||||
if (savedNetworkConfig == null) {
|
||||
dbExecutor.execute(() -> {
|
||||
// load savedNetworkConfig before starting hotspot
|
||||
dbExecutor.execute(() -> {
|
||||
loadSavedNetworkConfig();
|
||||
androidExecutor.runOnUiThread(createGroup);
|
||||
loadSavedNetworkConfig();
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
WifiP2pConfig config = new WifiP2pConfig.Builder()
|
||||
.setGroupOperatingBand(GROUP_OWNER_BAND_2GHZ)
|
||||
.setNetworkName(savedNetworkConfig.ssid)
|
||||
.setPassphrase(savedNetworkConfig.password)
|
||||
.build();
|
||||
acquireLock();
|
||||
wifiP2pManager.createGroup(channel, config, this);
|
||||
});
|
||||
} else {
|
||||
// savedNetworkConfig was already loaded, create group now
|
||||
createGroup.run();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
wifiP2pManager.createGroup(channel, listener);
|
||||
acquireLock();
|
||||
wifiP2pManager.createGroup(channel, this);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
// this should never happen, because we request permissions before
|
||||
@@ -244,45 +157,54 @@ class HotspotManager {
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void restartWifiP2pFramework(int attempt) {
|
||||
LOG.info("retrying to start WifiP2p framework");
|
||||
if (attempt < MAX_FRAMEWORK_ATTEMPTS) {
|
||||
if (SDK_INT >= 27 && channel != null) channel.close();
|
||||
channel = null;
|
||||
handler.postDelayed(() -> startWifiP2pFramework(attempt + 1),
|
||||
RETRY_DELAY_MILLIS);
|
||||
@Override
|
||||
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
|
||||
public void onSuccess() {
|
||||
requestGroupInfo(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
|
||||
public void onFailure(int reason) {
|
||||
if (reason == BUSY) {
|
||||
// Hotspot already running
|
||||
requestGroupInfo(1);
|
||||
} else if (reason == P2P_UNSUPPORTED) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"p2p unsupported"));
|
||||
} else if (reason == ERROR) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed, "p2p error"));
|
||||
} else if (reason == NO_SERVICE_REQUESTS) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"no service requests"));
|
||||
} else {
|
||||
releaseHotspotWithError(
|
||||
ctx.getString(R.string.hotspot_error_framework_busy));
|
||||
// all cases covered, in doubt set to error
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed_unknown,
|
||||
reason));
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void stopWifiP2pHotspot() {
|
||||
if (channel == null) return;
|
||||
wifiP2pManager.removeGroup(channel, new ActionListener() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
closeChannelAndReleaseLocks();
|
||||
releaseHotspot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(int reason) {
|
||||
// not propagating back error
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Error removing Wifi P2P group: " + reason);
|
||||
}
|
||||
closeChannelAndReleaseLocks();
|
||||
releaseHotspot();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("WakelockTimeout")
|
||||
private void acquireLocks() {
|
||||
// FLAG_KEEP_SCREEN_ON is not respected on some Huawei devices.
|
||||
wakeLock = powerManager.newWakeLock(FULL_WAKE_LOCK, lockTag);
|
||||
wakeLock.acquire();
|
||||
private void acquireLock() {
|
||||
// WIFI_MODE_FULL has no effect on API >= 29
|
||||
int lockType =
|
||||
SDK_INT >= 29 ? WIFI_MODE_FULL_HIGH_PERF : WIFI_MODE_FULL;
|
||||
@@ -290,21 +212,22 @@ class HotspotManager {
|
||||
wifiLock.acquire();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void releaseHotspot() {
|
||||
listener.onHotspotStopped();
|
||||
closeChannelAndReleaseLock();
|
||||
}
|
||||
|
||||
private void releaseHotspotWithError(String error) {
|
||||
listener.onHotspotError(error);
|
||||
closeChannelAndReleaseLocks();
|
||||
closeChannelAndReleaseLock();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void closeChannelAndReleaseLocks() {
|
||||
if (SDK_INT >= 27 && channel != null) channel.close();
|
||||
private void closeChannelAndReleaseLock() {
|
||||
if (SDK_INT >= 27) channel.close();
|
||||
channel = null;
|
||||
if (wakeLock.isHeld()) wakeLock.release();
|
||||
if (wifiLock.isHeld()) wifiLock.release();
|
||||
wifiLock.release();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void requestGroupInfo(int attempt) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("requestGroupInfo attempt: " + attempt);
|
||||
@@ -347,22 +270,27 @@ class HotspotManager {
|
||||
LOG.info("group is null");
|
||||
return false;
|
||||
} else if (!group.getNetworkName().startsWith("DIRECT-")) {
|
||||
LOG.info("received networkName without prefix 'DIRECT-'");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("received networkName without prefix 'DIRECT-': " +
|
||||
group.getNetworkName());
|
||||
}
|
||||
return false;
|
||||
} else if (SDK_INT >= 29) {
|
||||
// if we get here, the savedNetworkConfig must have a value
|
||||
String networkName = requireNonNull(savedNetworkConfig).ssid;
|
||||
String networkName = savedNetworkConfig.ssid;
|
||||
if (!networkName.equals(group.getNetworkName())) {
|
||||
LOG.info("expected networkName does not match received one");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("expected networkName: " + networkName);
|
||||
LOG.info("received networkName: " + group.getNetworkName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void retryRequestingGroupInfo(int attempt) {
|
||||
LOG.info("retrying to request group info");
|
||||
LOG.info("retrying");
|
||||
// On some devices we need to wait for the group info to become available
|
||||
if (attempt < MAX_GROUP_INFO_ATTEMPTS) {
|
||||
handler.postDelayed(() -> requestGroupInfo(attempt + 1),
|
||||
@@ -375,13 +303,19 @@ class HotspotManager {
|
||||
|
||||
@UiThread
|
||||
private void requestGroupInfoForConnection() {
|
||||
LOG.info("requestGroupInfo for connection");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("requestGroupInfo for connection");
|
||||
}
|
||||
GroupInfoListener groupListener = group -> {
|
||||
if (group != null) {
|
||||
listener.onPeersUpdated(group.getClientList().size());
|
||||
if (group == null || group.getClientList().isEmpty()) {
|
||||
handler.postDelayed(this::requestGroupInfoForConnection,
|
||||
RETRY_DELAY_MILLIS);
|
||||
} else {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("client list " + group.getClientList());
|
||||
}
|
||||
listener.onDeviceConnected();
|
||||
}
|
||||
handler.postDelayed(this::requestGroupInfoForConnection,
|
||||
RETRY_DELAY_MILLIS);
|
||||
};
|
||||
try {
|
||||
if (channel == null) return;
|
||||
@@ -437,16 +371,26 @@ class HotspotManager {
|
||||
return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;";
|
||||
}
|
||||
|
||||
// exclude chars that are easy to confuse: 0 O, 5 S, 1 l I
|
||||
private static final String chars =
|
||||
"2346789ABCDEFGHJKLMNPQRTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
private static final String digits = "123456789"; // avoid 0
|
||||
private static final String letters = "abcdefghijkmnopqrstuvwxyz"; // no l
|
||||
private static final String LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // no I, O
|
||||
|
||||
private String getRandomString(int length) {
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
c[i] = chars.charAt(random.nextInt(chars.length()));
|
||||
if (random.nextBoolean()) {
|
||||
c[i] = random(digits);
|
||||
} else if (random.nextBoolean()) {
|
||||
c[i] = random(letters);
|
||||
} else {
|
||||
c[i] = random(LETTERS);
|
||||
}
|
||||
}
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
private char random(String universe) {
|
||||
return universe.charAt(random.nextInt(universe.length()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,19 +57,16 @@ abstract class HotspotState {
|
||||
return websiteConfig;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
boolean wasNotYetConsumed() {
|
||||
return !consumed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this state as consumed, i.e. the UI has already done something
|
||||
* as a result of the state changing to this. This can be used in order
|
||||
* to not repeat actions such as showing fragments on rotation changes.
|
||||
*/
|
||||
@UiThread
|
||||
void consume() {
|
||||
boolean consume() {
|
||||
boolean old = consumed;
|
||||
consumed = true;
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ class HotspotViewModel extends DbViewModel
|
||||
|
||||
private final MutableLiveData<HotspotState> state =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Integer> peersConnected =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveEvent<Boolean> peerConnected =
|
||||
new MutableLiveEvent<>();
|
||||
private final MutableLiveEvent<Uri> savedApkToUri =
|
||||
new MutableLiveEvent<>();
|
||||
|
||||
@@ -137,8 +137,14 @@ class HotspotViewModel extends DbViewModel
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void onPeersUpdated(int peers) {
|
||||
peersConnected.setValue(peers);
|
||||
public void onDeviceConnected() {
|
||||
peerConnected.setEvent(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHotspotStopped() {
|
||||
LOG.info("stopping webserver");
|
||||
ioExecutor.execute(webServerManager::stopWebServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,7 +170,7 @@ class HotspotViewModel extends DbViewModel
|
||||
public void onWebServerError() {
|
||||
state.postValue(new HotspotError(getApplication()
|
||||
.getString(R.string.hotspot_error_web_server_start)));
|
||||
stopHotspot();
|
||||
hotspotManager.stopWifiP2pHotspot();
|
||||
}
|
||||
|
||||
void exportApk(Uri uri) {
|
||||
@@ -213,8 +219,8 @@ class HotspotViewModel extends DbViewModel
|
||||
return state;
|
||||
}
|
||||
|
||||
LiveData<Integer> getPeersConnectedEvent() {
|
||||
return peersConnected;
|
||||
LiveEvent<Boolean> getPeerConnectedEvent() {
|
||||
return peerConnected;
|
||||
}
|
||||
|
||||
LiveEvent<Uri> getSavedApkToUri() {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
@@ -18,6 +16,7 @@ import org.briarproject.briar.android.hotspot.HotspotState.HotspotStarted;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@@ -61,23 +60,19 @@ public class QrHotspotFragment extends Fragment {
|
||||
TextView qrIntroView = v.findViewById(R.id.qrIntroView);
|
||||
ImageView qrCodeView = v.findViewById(R.id.qrCodeView);
|
||||
|
||||
boolean forWifi = requireArguments().getBoolean(ARG_FOR_WIFI_CONNECT);
|
||||
|
||||
qrIntroView.setText(forWifi ? R.string.hotspot_qr_wifi :
|
||||
R.string.hotspot_qr_site);
|
||||
|
||||
Consumer<HotspotStarted> consumer;
|
||||
if (requireArguments().getBoolean(ARG_FOR_WIFI_CONNECT)) {
|
||||
qrIntroView.setText(R.string.hotspot_qr_wifi);
|
||||
consumer = state ->
|
||||
qrCodeView.setImageBitmap(state.getNetworkConfig().qrCode);
|
||||
} else {
|
||||
qrIntroView.setText(R.string.hotspot_qr_site);
|
||||
consumer = state ->
|
||||
qrCodeView.setImageBitmap(state.getWebsiteConfig().qrCode);
|
||||
}
|
||||
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
|
||||
if (state instanceof HotspotStarted) {
|
||||
HotspotStarted s = (HotspotStarted) state;
|
||||
Bitmap qrCode = forWifi ? s.getNetworkConfig().qrCode :
|
||||
s.getWebsiteConfig().qrCode;
|
||||
if (qrCode == null) {
|
||||
Toast.makeText(requireContext(), R.string.error,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
qrCodeView.setImageResource(R.drawable.ic_image_broken);
|
||||
} else {
|
||||
qrCodeView.setImageBitmap(qrCode);
|
||||
}
|
||||
consumer.accept((HotspotStarted) state);
|
||||
}
|
||||
});
|
||||
return v;
|
||||
|
||||
@@ -14,18 +14,21 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.list;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
import static org.briarproject.briar.android.hotspot.WebServer.PORT;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -46,7 +49,7 @@ class WebServerManager {
|
||||
private final WebServer webServer;
|
||||
private final DisplayMetrics dm;
|
||||
|
||||
private volatile WebServerListener listener;
|
||||
private WebServerListener listener;
|
||||
|
||||
@Inject
|
||||
WebServerManager(Application ctx) {
|
||||
@@ -54,7 +57,6 @@ class WebServerManager {
|
||||
dm = ctx.getResources().getDisplayMetrics();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void setListener(WebServerListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
@@ -108,4 +110,16 @@ class WebServerManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<NetworkInterface> getNetworkInterfaces() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> ifaces =
|
||||
NetworkInterface.getNetworkInterfaces();
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.StartupFailureActivity;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.blog.FeedFragment;
|
||||
@@ -74,7 +73,6 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
|
||||
@@ -252,11 +250,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
|
||||
private void exitIfStartupFailed(Intent intent) {
|
||||
if (intent.getBooleanExtra(EXTRA_STARTUP_FAILED, false)) {
|
||||
// Launch StartupFailureActivity in its own process, then exit
|
||||
Intent i = new Intent(this, StartupFailureActivity.class);
|
||||
i.putExtra(EXTRA_START_RESULT,
|
||||
intent.getSerializableExtra(EXTRA_START_RESULT));
|
||||
startActivity(i);
|
||||
finish();
|
||||
LOG.info("Exiting");
|
||||
System.exit(0);
|
||||
|
||||
@@ -47,8 +47,6 @@ public class ReceiveFragment extends Fragment {
|
||||
private Button button;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
private boolean checkForStateLoss = false;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -75,10 +73,6 @@ public class ReceiveFragment extends Fragment {
|
||||
.observeEvent(getViewLifecycleOwner(), this::onOldTaskResumed);
|
||||
viewModel.getState()
|
||||
.observe(getViewLifecycleOwner(), this::onStateChanged);
|
||||
|
||||
// need to check for lost ViewModel state when creating with prior state
|
||||
if (savedInstanceState != null) checkForStateLoss = true;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -90,23 +84,6 @@ public class ReceiveFragment extends Fragment {
|
||||
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// This code gets called *after* launcher had a chance
|
||||
// to return the activity result.
|
||||
if (checkForStateLoss && viewModel.hasNoState()) {
|
||||
// We were recreated, but have lost the ViewModel state,
|
||||
// because our activity was destroyed.
|
||||
//
|
||||
// Remove the current fragment from the stack
|
||||
// to prevent duplicates on the back stack.
|
||||
getParentFragmentManager().popBackStack();
|
||||
// Start again (picks up existing task or allows to start a new one)
|
||||
viewModel.startReceiveData();
|
||||
}
|
||||
}
|
||||
|
||||
private void onOldTaskResumed(boolean resumed) {
|
||||
if (resumed) {
|
||||
Toast.makeText(requireContext(),
|
||||
@@ -127,8 +104,6 @@ public class ReceiveFragment extends Fragment {
|
||||
|
||||
private void onDocumentChosen(@Nullable Uri uri) {
|
||||
if (uri == null) return;
|
||||
// we just got our document, so don't treat this as a state loss
|
||||
checkForStateLoss = false;
|
||||
viewModel.importData(uri);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,12 +75,6 @@ class RemovableDriveViewModel extends DbViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
boolean hasNoState() {
|
||||
return action.getLastValue() == null && state.getValue() == null &&
|
||||
task == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this as soon as it becomes available.
|
||||
*/
|
||||
|
||||
@@ -51,8 +51,6 @@ public class SendFragment extends Fragment {
|
||||
private Button button;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
private boolean checkForStateLoss = false;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -82,9 +80,6 @@ public class SendFragment extends Fragment {
|
||||
viewModel.getState()
|
||||
.observe(getViewLifecycleOwner(), this::onStateChanged);
|
||||
|
||||
// need to check for lost ViewModel state when creating with prior state
|
||||
if (savedInstanceState != null) checkForStateLoss = true;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -96,23 +91,6 @@ public class SendFragment extends Fragment {
|
||||
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// This code gets called *after* launcher had a chance
|
||||
// to return the activity result.
|
||||
if (checkForStateLoss && viewModel.hasNoState()) {
|
||||
// We were recreated, but have lost the ViewModel state,
|
||||
// because our activity was destroyed.
|
||||
//
|
||||
// Remove the current fragment from the stack
|
||||
// to prevent duplicates on the back stack.
|
||||
getParentFragmentManager().popBackStack();
|
||||
// Start again (picks up existing task or allows to start a new one)
|
||||
viewModel.startSendData();
|
||||
}
|
||||
}
|
||||
|
||||
private void onOldTaskResumed(boolean resumed) {
|
||||
if (resumed) {
|
||||
Toast.makeText(requireContext(),
|
||||
@@ -149,8 +127,6 @@ public class SendFragment extends Fragment {
|
||||
|
||||
private void onDocumentCreated(@Nullable Uri uri) {
|
||||
if (uri == null) return;
|
||||
// we just got our document, so don't treat this as a state loss
|
||||
checkForStateLoss = false;
|
||||
viewModel.exportData(uri);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.reporting;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.logging.LogEncrypter;
|
||||
@@ -11,7 +10,6 @@ import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -30,8 +28,6 @@ class BriarExceptionHandler implements UncaughtExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
if (IS_DEBUG_BUILD) Log.w("Uncaught exception", e);
|
||||
|
||||
// encrypt logs to disk before handing over to new process
|
||||
// the intent has limited space, so we can't reliably store them there.
|
||||
byte[] logKey = logEncrypter.encryptLogs();
|
||||
|
||||
@@ -62,6 +62,7 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
@@ -340,6 +341,11 @@ public class UiUtils {
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void putShowAdvancedExtra(Intent i) {
|
||||
i.putExtra(SDK_INT <= 28 ? "android.content.extra.SHOW_ADVANCED" :
|
||||
"android.provider.extra.SHOW_ADVANCED", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if location is enabled,
|
||||
* or it isn't required due to this being a SDK < 28 device.
|
||||
@@ -561,5 +567,4 @@ public class UiUtils {
|
||||
activity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE |
|
||||
SOFT_INPUT_STATE_HIDDEN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.view;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ImageView;
|
||||
@@ -30,7 +29,6 @@ import im.delight.android.identicons.IdenticonDrawable;
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.graphics.Typeface.BOLD;
|
||||
import static android.util.TypedValue.COMPLEX_UNIT_PX;
|
||||
import static androidx.appcompat.content.res.AppCompatResources.getDrawable;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||
import static org.briarproject.briar.android.util.UiUtils.resolveAttribute;
|
||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.NONE;
|
||||
@@ -179,14 +177,14 @@ public class AuthorView extends ConstraintLayout {
|
||||
case RSS_FEED:
|
||||
avatarIcon.setVisibility(INVISIBLE);
|
||||
date.setVisibility(VISIBLE);
|
||||
setRssVectorAvatar();
|
||||
avatar.setImageResource(R.drawable.ic_rss_feed);
|
||||
setAvatarSize(R.dimen.blogs_avatar_normal_size);
|
||||
setTextSize(authorName, R.dimen.text_size_small);
|
||||
break;
|
||||
case RSS_FEED_REBLOGGED:
|
||||
avatarIcon.setVisibility(INVISIBLE);
|
||||
date.setVisibility(VISIBLE);
|
||||
setRssVectorAvatar();
|
||||
avatar.setImageResource(R.drawable.ic_rss_feed);
|
||||
setAvatarSize(R.dimen.blogs_avatar_comment_size);
|
||||
setTextSize(authorName, R.dimen.text_size_tiny);
|
||||
break;
|
||||
@@ -206,16 +204,4 @@ public class AuthorView extends ConstraintLayout {
|
||||
v.setTextSize(COMPLEX_UNIT_PX, textSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies special hack to use AppCompat vector drawable support
|
||||
* when setting the RSS vector drawable to the avatar view.
|
||||
* {@link ImageView#setImageResource(int)} is not working as
|
||||
* {@link CircleImageView} is not using
|
||||
* {@link androidx.appcompat.widget.AppCompatImageView}.
|
||||
*/
|
||||
private void setRssVectorAvatar() {
|
||||
Drawable d = getDrawable(getContext(), R.drawable.ic_rss_feed);
|
||||
avatar.setImageDrawable(d);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,13 +42,10 @@ public interface AndroidNotificationManager {
|
||||
// that will sort below the main channels such as contacts
|
||||
String ONGOING_CHANNEL_OLD_ID = "zForegroundService";
|
||||
String ONGOING_CHANNEL_ID = "zForegroundService2";
|
||||
String FAILURE_CHANNEL_ID = "zStartupFailure";
|
||||
String REMINDER_CHANNEL_ID = "zSignInReminder";
|
||||
String HOTSPOT_CHANNEL_ID = "zHotspot";
|
||||
|
||||
// This channel is no longer used - keep the ID so we can remove the
|
||||
// channel from existing installations
|
||||
String FAILURE_CHANNEL_ID = "zStartupFailure";
|
||||
|
||||
// Actions for pending intents
|
||||
String ACTION_DISMISS_REMINDER = "dismissReminder";
|
||||
String ACTION_STOP_HOTSPOT = "stopHotspot";
|
||||
|
||||
@@ -5,30 +5,31 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:fillViewport="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/acceptButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_xlarge">
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/errorIcon"
|
||||
android:layout_width="@dimen/hero_square"
|
||||
android:layout_height="@dimen/hero_square"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/crashed"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHeight_max="128dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:srcCompat="@drawable/ic_crash"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
@@ -38,23 +39,25 @@
|
||||
android:id="@+id/crashed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/briar_crashed"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintBottom_toTopOf="@+id/fault"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorIcon" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorIcon"
|
||||
tools:layout_editor_absoluteY="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fault"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_large"
|
||||
android:gravity="center"
|
||||
android:text="@string/not_your_fault"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintBottom_toTopOf="@+id/pleaseSend"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -67,7 +70,8 @@
|
||||
android:layout_marginTop="@dimen/margin_large"
|
||||
android:gravity="center"
|
||||
android:text="@string/please_send_report"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintBottom_toTopOf="@+id/encrypted"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -80,7 +84,8 @@
|
||||
android:layout_marginTop="@dimen/margin_large"
|
||||
android:gravity="center"
|
||||
android:text="@string/report_is_encrypted"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -88,7 +93,7 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/declineButton"
|
||||
@@ -96,7 +101,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/acceptButton"
|
||||
app:layout_constraintEnd_toStartOf="@+id/acceptButton"
|
||||
app:layout_constraintHorizontal_weight="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -1,55 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||
android:fillViewport="true">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/errorIcon"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/alerts_and_states_error"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_xlarge">
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/sorry"
|
||||
android:textSize="@dimen/text_size_xlarge"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorIcon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/errorIcon"
|
||||
android:layout_width="@dimen/hero_square"
|
||||
android:layout_height="@dimen/hero_square"
|
||||
app:layout_constraintBottom_toTopOf="@+id/errorTitle"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:srcCompat="@drawable/alerts_and_states_error"
|
||||
app:tint="@color/briar_red_500"
|
||||
tools:ignore="ContentDescription" />
|
||||
<TextView
|
||||
android:id="@+id/errorMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorTitle"
|
||||
tools:text="@string/startup_failed_service_error" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/sorry"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
app:layout_constraintBottom_toTopOf="@+id/errorMessage"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorIcon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorTitle"
|
||||
tools:text="@string/startup_failed_service_error" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -1,102 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<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">
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:fillViewport="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tryAgainButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<TextView
|
||||
android:id="@+id/errorTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/connection_error_title"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/errorIcon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_xlarge">
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorTitle"
|
||||
app:srcCompat="@drawable/qr_code_error"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/errorIcon"
|
||||
android:layout_width="@dimen/hero_rect_width"
|
||||
android:layout_height="@dimen/hero_rect_height"
|
||||
app:layout_constraintBottom_toTopOf="@+id/errorTitle"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:srcCompat="@drawable/qr_code_error"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
tools:ignore="ContentDescription" />
|
||||
<TextView
|
||||
android:id="@+id/errorMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toTopOf="@+id/sendFeedback"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorIcon"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="error explanation" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/connection_error_title"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
app:layout_constraintBottom_toTopOf="@+id/errorMessage"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorIcon" />
|
||||
<TextView
|
||||
android:id="@+id/sendFeedback"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:text="@string/connection_error_feedback"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tryAgainButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorMessage" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/sendFeedback"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorTitle"
|
||||
tools:text="error explanation" />
|
||||
<Button
|
||||
android:id="@+id/tryAgainButton"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/try_again_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_weight="1"
|
||||
app:layout_constraintStart_toEndOf="@+id/cancelButton" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sendFeedback"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_large"
|
||||
android:text="@string/connection_error_feedback"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorMessage" />
|
||||
<Button
|
||||
android:id="@+id/cancelButton"
|
||||
style="@style/BriarButtonFlat.Negative"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tryAgainButton"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:layout_constraintHorizontal_weight="1"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancelButton"
|
||||
style="@style/BriarButtonFlat.Negative"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tryAgainButton"
|
||||
app:layout_constraintHorizontal_weight="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/tryAgainButton" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/tryAgainButton"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/try_again_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_weight="1"
|
||||
app:layout_constraintStart_toEndOf="@+id/cancelButton"
|
||||
app:layout_constraintTop_toBottomOf="@+id/scrollView" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<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"
|
||||
@@ -12,16 +12,18 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iconView"
|
||||
android:layout_width="@dimen/hero_square"
|
||||
android:layout_height="@dimen/hero_square"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/titleView"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintWidth_max="200dp"
|
||||
app:layout_constraintWidth_percent="0.4"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:srcCompat="@drawable/alerts_and_states_error"
|
||||
tools:tint="@color/briar_red_500" />
|
||||
@@ -30,8 +32,11 @@
|
||||
android:id="@+id/titleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginRight="32dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
app:layout_constraintBottom_toTopOf="@+id/textView"
|
||||
@@ -44,9 +49,10 @@
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_marginBottom="@dimen/margin_large"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -59,7 +65,7 @@
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/finish"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -67,4 +73,4 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</ScrollView>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/feedbackButton"
|
||||
tools:layout="@layout/fragment_hotspot_fallback" />
|
||||
tools:layout="@layout/fragment_hotspot_save_apk" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/site4View"
|
||||
tools:layout="@layout/fragment_hotspot_fallback" />
|
||||
tools:layout="@layout/fragment_hotspot_save_apk" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
@@ -1,89 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
tools:context=".android.hotspot.HotspotIntroFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginLeft="64dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="64dp"
|
||||
android:layout_marginRight="64dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/introView"
|
||||
app:layout_constraintDimensionRatio="1,1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="spread_inside"
|
||||
app:srcCompat="@drawable/ic_nickname"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="@dimen/hero_square"
|
||||
android:layout_height="@dimen/hero_square"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
app:layout_constraintBottom_toTopOf="@+id/introView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:srcCompat="@drawable/ic_wifi_tethering"
|
||||
tools:ignore="ContentDescription" />
|
||||
<TextView
|
||||
android:id="@+id/introView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/hotspot_intro"
|
||||
app:layout_constraintBottom_toTopOf="@+id/startButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_marginBottom="@dimen/margin_large"
|
||||
android:text="@string/hotspot_intro"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/startButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView" />
|
||||
<Button
|
||||
android:id="@+id/startButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:drawablePadding="6dp"
|
||||
android:text="@string/hotspot_button_start_sharing"
|
||||
app:drawableLeftCompat="@drawable/ic_wifi_tethering"
|
||||
app:drawableStartCompat="@drawable/ic_wifi_tethering"
|
||||
app:drawableTint="@color/button_text"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.812"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/introView"
|
||||
app:layout_constraintVertical_bias="1.0"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/startButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/hotspot_button_start_sharing"
|
||||
app:drawableLeftCompat="@drawable/ic_wifi_tethering"
|
||||
app:drawableStartCompat="@drawable/ic_wifi_tethering"
|
||||
app:drawableTint="@color/button_text"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/startButton"
|
||||
app:layout_constraintEnd_toStartOf="@+id/progressTextView"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="@+id/startButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/startButton"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/startButton"
|
||||
app:layout_constraintEnd_toStartOf="@+id/progressTextView"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="@+id/startButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/startButton"
|
||||
tools:visibility="visible" />
|
||||
<TextView
|
||||
android:id="@+id/progressTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:text="@string/hotspot_progress_text_start"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/startButton"
|
||||
app:layout_constraintEnd_toEndOf="@+id/startButton"
|
||||
app:layout_constraintStart_toEndOf="@+id/progressBar"
|
||||
app:layout_constraintTop_toTopOf="@+id/startButton"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/progressTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_large"
|
||||
android:layout_marginLeft="@dimen/margin_large"
|
||||
android:text="@string/hotspot_progress_text_start"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/startButton"
|
||||
app:layout_constraintEnd_toEndOf="@+id/startButton"
|
||||
app:layout_constraintStart_toEndOf="@+id/progressBar"
|
||||
app:layout_constraintTop_toTopOf="@+id/startButton"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:background="@android:color/white"
|
||||
android:background="@color/briar_primary"
|
||||
android:padding="8dp"
|
||||
android:textColor="@color/briar_primary"
|
||||
android:textColor="@color/briar_text_primary_inverse"
|
||||
android:typeface="monospace"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -71,9 +71,9 @@
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:background="@android:color/white"
|
||||
android:background="@color/briar_primary"
|
||||
android:padding="8dp"
|
||||
android:textColor="@color/briar_primary"
|
||||
android:textColor="@color/briar_text_primary_inverse"
|
||||
android:typeface="monospace"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
android:id="@id/fallbackTitleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/hotspot_help_fallback_title"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
@@ -6,10 +6,9 @@
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/connectedView"
|
||||
app:layout_constraintBottom_toTopOf="@+id/connectedButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
@@ -52,32 +51,20 @@
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintBottom_toTopOf="@+id/connectedButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connectedView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/hotspot_no_peers_connected"
|
||||
app:layout_constraintTop_toBottomOf="@+id/coordinatorLayout"
|
||||
app:layout_constraintBottom_toTopOf="@+id/connectedButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/connectedButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/hotspot_button_connected"
|
||||
app:drawableLeftCompat="@drawable/ic_check_white"
|
||||
app:drawableStartCompat="@drawable/ic_check_white"
|
||||
@@ -92,9 +79,7 @@
|
||||
style="@style/BriarButtonFlat.Negative"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:layout_margin="16dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/hotspot_button_stop_sharing"
|
||||
app:drawableLeftCompat="@drawable/ic_portable_wifi_off"
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/briar_primary"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
@@ -13,30 +12,30 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="@drawable/abc_ic_ab_back_material"
|
||||
tools:title="Onboarding Fullscreen Dialog" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/briar_primary"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_xlarge">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="@dimen/hero_square"
|
||||
android:layout_height="@dimen/hero_square"
|
||||
app:layout_constraintBottom_toTopOf="@+id/contentView"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:srcCompat="@drawable/ic_info_white"
|
||||
app:tint="@color/briar_text_secondary_inverse"
|
||||
tools:ignore="ContentDescription" />
|
||||
@@ -45,25 +44,26 @@
|
||||
android:id="@+id/contentView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_margin="16dp"
|
||||
android:textColor="@color/briar_text_secondary_inverse"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/got_it"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/got_it" />
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="@dimen/hero_square"
|
||||
android:layout_height="@dimen/hero_square"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/introView"
|
||||
app:layout_constraintDimensionRatio="1,1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintVertical_chainStyle="spread"
|
||||
app:layout_constraintWidth_percent="0.4"
|
||||
app:srcCompat="@drawable/ic_transfer_data"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
@@ -29,11 +29,12 @@
|
||||
android:id="@+id/introView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_marginBottom="@dimen/margin_large"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/removable_drive_intro"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/sendButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -44,8 +45,7 @@
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_large"
|
||||
android:layout_marginTop="@dimen/margin_large"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/removable_drive_title_send"
|
||||
app:layout_constraintBottom_toTopOf="@+id/receiveButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -56,7 +56,7 @@
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/removable_drive_title_receive"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
||||
@@ -12,17 +12,18 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="@dimen/hero_rect_width"
|
||||
android:layout_height="@dimen/hero_rect_height"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/progressBar"
|
||||
app:layout_constraintDimensionRatio="1,2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:srcCompat="@drawable/ic_transfer_data_receive"
|
||||
app:layout_constraintVertical_chainStyle="spread"
|
||||
app:layout_constraintWidth_max="300dp"
|
||||
app:layout_constraintWidth_percent="0.6"
|
||||
app:srcCompat="@drawable/ic_transfer_data_send"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ProgressBar
|
||||
@@ -30,8 +31,7 @@
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_margin="32dp"
|
||||
android:indeterminate="true"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toTopOf="@+id/introTextView"
|
||||
@@ -44,11 +44,11 @@
|
||||
android:id="@+id/introTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_marginBottom="@dimen/margin_large"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/removable_drive_receive_intro"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/fileButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -59,7 +59,7 @@
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:layout_margin="16dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/removable_drive_receive_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -12,16 +12,17 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="@dimen/hero_rect_width"
|
||||
android:layout_height="@dimen/hero_rect_height"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/progressBar"
|
||||
app:layout_constraintDimensionRatio="1,2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintWidth_percent="0.6"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintVertical_chainStyle="spread"
|
||||
app:layout_constraintWidth_max="300dp"
|
||||
app:srcCompat="@drawable/ic_transfer_data_send"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
@@ -30,8 +31,7 @@
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_margin="32dp"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toTopOf="@+id/introTextView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -43,11 +43,11 @@
|
||||
android:id="@+id/introTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_xlarge"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:layout_marginBottom="@dimen/margin_large"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/removable_drive_send_intro"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/fileButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -58,7 +58,7 @@
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_large"
|
||||
android:layout_margin="16dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/removable_drive_send_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/listitem_vertical_margin"
|
||||
android:paddingLeft="@dimen/listitem_vertical_margin"
|
||||
android:paddingEnd="@dimen/listitem_vertical_margin"
|
||||
android:paddingRight="@dimen/listitem_vertical_margin"
|
||||
android:paddingBottom="@dimen/listitem_vertical_margin"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
|
||||
@@ -1,111 +1,43 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Добре дошли в Briar</string>
|
||||
<string name="setup_name_explanation">Прякорът ви ще бъде видим до всяка ваша публикация. Няма да можете да го промените след като създадете профил.</string>
|
||||
<string name="setup_next">Напред</string>
|
||||
<string name="setup_password_intro">Изберете парола</string>
|
||||
<string name="setup_password_explanation">Профилът в Briar се съхранява шифриран на устройството ви, а не в облака. Ако забравите паролата си или премахнете Briar, няма начин да го възстановите.\n\nИзберете дълга, трудна за отгатване парола, например: четири случайни думи или десет случайни букви, числа и знаци.</string>
|
||||
<string name="setup_doze_title">Свързаност на заден план</string>
|
||||
<string name="setup_doze_intro">За да получавате съобщения, Briar трябва да е свързан на заден план.</string>
|
||||
<string name="setup_doze_explanation">За да получавате съобщения, Briar трябва да е свързан на заден план. Изключете оптимизацията на батерията, за да може Briar да остане свързан.</string>
|
||||
<string name="setup_doze_button">Разрешаване на свързаност</string>
|
||||
<string name="choose_nickname">Изберете прякор</string>
|
||||
<string name="setup_next">Следващ</string>
|
||||
<string name="choose_nickname">Изберете име</string>
|
||||
<string name="choose_password">Изберете парола</string>
|
||||
<string name="confirm_password">Потвърдете парола</string>
|
||||
<string name="name_too_long">Името е твърде дълго</string>
|
||||
<string name="password_too_weak">Паролата е твърде слаба</string>
|
||||
<string name="passwords_do_not_match">Паролите не съвпадат</string>
|
||||
<string name="create_account_button">Създаване на профил</string>
|
||||
<string name="more_info">Повече информация</string>
|
||||
<string name="don_t_ask_again">Спиране на този въпрос</string>
|
||||
<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>
|
||||
<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="try_again">Грешна парола, опитайте пак</string>
|
||||
<string name="sign_in_button">Вход</string>
|
||||
<string name="forgotten_password">Забравена парола</string>
|
||||
<string name="dialog_title_lost_password">Забравена парола</string>
|
||||
<string name="dialog_message_lost_password">Профилът в Briar се съхранява шифриран на вашето устройство, а не в облака и за това паролата не може да бъде сменена. Желаете ли да профилът да бъде премахнат и да бъде направен нов?\n\nВнимание: Профилът, контактите и съобщенията ще бъдат безвъзвратно загубени.</string>
|
||||
<string name="dialog_message_lost_password">Briar профилът се съхранява криптиран във вашето устройство, не в облака, така че не можем да зададем нова парола. Искате ли да изтриете профила си и да започнете отначало?\n\nВнимание: Вашият профил, контакти и съобщения ще бъдат изтрити завинаги.</string>
|
||||
<string name="startup_failed_notification_title">Briar не можа да стартира</string>
|
||||
<string name="startup_failed_notification_text">Докоснете за повече информация.</string>
|
||||
<string name="startup_failed_activity_title">Неуспешно стартиране</string>
|
||||
<string name="startup_failed_db_error">По някаква причина банката от данни на Briar е непоправимо повредена. Вашият профил, данни и всичките ви контакти са загубени. За жалост, се налага да преинсталирате Briar или да създадете нов профил, избирайки „Забравена парола“ от екрана за вход.</string>
|
||||
<string name="startup_failed_data_too_old_error">Вашия профил е създаден със по-ранно издание на приложението и не може да бъде отворен. Трябва или да инсталирате по-ранното издание или да създадете нов профил, избирайки „Забравена парола“ от екрана за вход.</string>
|
||||
<string name="startup_failed_data_too_new_error">Тава издание на приложението е твърде старо. Обновете до последно издание и опитайте отново.</string>
|
||||
<string name="startup_failed_service_error">Briar не може да стартира задължителна приставка. Обикновено преинсталирането на Briar решава този проблем. Имайте предвид, че ще изгубите профила си и всички свързани с него данни, тъй като Briar не ги съхранява в централни сървъри.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Това е тестова версия на Briar. Вашият акаунт ще бъде изтече след %d ден и не може да бъде подновена.</item>
|
||||
<item quantity="other">Това е изпитателно издание на Briar. Валидността на профила ще изтече след %d дена и не може да бъде подновена.</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">Софтуерът е с изтекъл срок.\nБлагодарим за изпитването!</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>
|
||||
<string name="startup_failed_service_error">Briar не успя да стартира задължителен плъгин. Обикновено преинсталирането на Briar решава този проблем. Моля, имайте предвид, че ще изгубите профила си и всички данни, асоциирани с него, тъй като Briar не съхранява данните ви в централни сървъри.</string>
|
||||
<string name="expiry_date_reached">Софтуерът е невалиден.\nБлагодарим ви за тестването!</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Отваря навигационната лента</string>
|
||||
<string name="nav_drawer_close_description">Затваря навигационната лента</string>
|
||||
<string name="nav_drawer_open_description">Отвори навигационно чекмедже</string>
|
||||
<string name="nav_drawer_close_description">Затвори навигационно чекмедже</string>
|
||||
<string name="contact_list_button">Контакти</string>
|
||||
<string name="groups_button">Частни групи</string>
|
||||
<string name="forums_button">Форуми</string>
|
||||
<string name="blogs_button">Блогове</string>
|
||||
<!--This is part of the main menu. The app will be locked when this is tapped.-->
|
||||
<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-->
|
||||
<!--Transports-->
|
||||
<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">Същата безжична мрежа</string>
|
||||
<string name="lan_device_status_on">Устройството е свързан с безжична мрежа</string>
|
||||
<string name="lan_device_status_off">Устройството не е свързан с безжична мрежа</string>
|
||||
<string name="lan_plugin_status_enabling">Briar се свързва с безжична мрежа</string>
|
||||
<string name="lan_plugin_status_active">Briar е свързан с безжична мрежа</string>
|
||||
<string name="lan_plugin_status_inactive">Briar не е свързан с безжична мрежа</string>
|
||||
<string name="lan_plugin_status_disabled">Briar е настроен да не използва безжична мрежа</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>
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">Отписани сте от Briar</string>
|
||||
<string name="reminder_notification_text">Докоснете за повторно влизане.</string>
|
||||
<string name="reminder_notification_channel_title">Напомняне за вход в Briar</string>
|
||||
<string name="reminder_notification_dismiss">Отказ</string>
|
||||
<string name="ongoing_notification_title">Вписани в Briar</string>
|
||||
<string name="ongoing_notification_text">Докоснете за отваряне на Briar.</string>
|
||||
<string name="ongoing_notification_title">Вписан сте в Briar</string>
|
||||
<string name="ongoing_notification_text">Докоснете, за да отворите Briar.</string>
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="one">Ново лично съобщение.</item>
|
||||
<item quantity="other">%d нови лични съобщения.</item>
|
||||
@@ -115,543 +47,281 @@
|
||||
<item quantity="other">%d нови групови съобщения.</item>
|
||||
</plurals>
|
||||
<plurals name="forum_post_notification_text">
|
||||
<item quantity="one">Нова публикация във форум.</item>
|
||||
<item quantity="other">%d нови публикации във форуми.</item>
|
||||
<item quantity="one">Нова форумна публикация.</item>
|
||||
<item quantity="other">%d нови форумни публикации.</item>
|
||||
</plurals>
|
||||
<plurals name="blog_post_notification_text">
|
||||
<item quantity="one">Нова публикация в блог.</item>
|
||||
<item quantity="other">%d нови публикации в блогове.</item>
|
||||
<item quantity="one">Нова блог публикация.</item>
|
||||
<item quantity="other">%d нови блог публикации.</item>
|
||||
</plurals>
|
||||
<!--Misc-->
|
||||
<string name="now">току-що</string>
|
||||
<string name="show">Показване</string>
|
||||
<string name="hide">Скриване</string>
|
||||
<string name="ok">Добре</string>
|
||||
<string name="now">сега</string>
|
||||
<string name="show">Покажи</string>
|
||||
<string name="hide">Скрий</string>
|
||||
<string name="ok">ОК</string>
|
||||
<string name="cancel">Отказ</string>
|
||||
<string name="got_it">Разбрах</string>
|
||||
<string name="delete">Изтриване</string>
|
||||
<string name="accept">Приемане</string>
|
||||
<string name="decline">Отказване</string>
|
||||
<string name="online">На линия</string>
|
||||
<string name="offline">Извън линия</string>
|
||||
<string name="send">Изпращане</string>
|
||||
<string name="allow">Разрешаване</string>
|
||||
<string name="open">Отваряне</string>
|
||||
<string name="change">Променяне</string>
|
||||
<string name="start">Старт</string>
|
||||
<string name="delete">Изтрий</string>
|
||||
<string name="accept">Приеми</string>
|
||||
<string name="decline">Откажи</string>
|
||||
<string name="options">Опции</string>
|
||||
<string name="online">Онлайн</string>
|
||||
<string name="offline">Офлайн</string>
|
||||
<string name="send">Изпрати</string>
|
||||
<string name="allow">Позволи</string>
|
||||
<string name="open">Отвори</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="status_heading">Състояние</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Няма контакти</string>
|
||||
<string name="no_contacts_action">Докоснете иконата с +, за да добавите контакти</string>
|
||||
<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>
|
||||
<string name="image_attach_error_too_big">Изображението е твърде голямо. Има ограничение от %dМБ.</string>
|
||||
<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Двамата трябва да натиснете „Start“ едновременно.</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."-->
|
||||
<string name="auto_delete_msg_you_disabled">Съобщенията ви няма да изчезнат. %1$s</string>
|
||||
<!--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."-->
|
||||
<string name="auto_delete_msg_contact_enabled">Съобщението от %1$s ще изчезне след %2$s. %3$s</string>
|
||||
<plurals name="duration_minutes">
|
||||
<item quantity="one">%d минута</item>
|
||||
<item quantity="other">%d минути</item>
|
||||
</plurals>
|
||||
<plurals name="duration_hours">
|
||||
<item quantity="one">%d час</item>
|
||||
<item quantity="other">%d часа</item>
|
||||
</plurals>
|
||||
<plurals name="duration_days">
|
||||
<item quantity="one">%d ден</item>
|
||||
<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="auto_delete_msg_contact_disabled">Съобщението от %1$s няма да изчезне. %2$s</string>
|
||||
<string name="tap_to_learn_more">Научете повече</string>
|
||||
<string name="auto_delete_changed_warning_title">Промяна при изчезващи съобщения</string>
|
||||
<string name="auto_delete_changed_warning_message_enabled">Откакто съставяте съобщението е бил включен механизмът за изчезващи съобщения.</string>
|
||||
<string name="auto_delete_changed_warning_message_disabled">Откакто съставяте съобщението е бил изключен механизмът за изчезващи съобщения.</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>
|
||||
<string name="contact_deleted_toast">Контактът е премахнат</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Вие</string>
|
||||
<string name="save_image">Запазване на изображение</string>
|
||||
<string name="dialog_title_save_image">Запазване на изображението?</string>
|
||||
<string name="dialog_message_save_image">Запазвайки изображението ще дадете възможност на други приложения да го достъпват.\n\nСигурни ли сте, че желаете да бъде запазено?</string>
|
||||
<string name="save_image_success">Изображението е запазено</string>
|
||||
<string name="save_image_error">Грешка при запазване на изображение</string>
|
||||
<string name="dialog_title_no_image_support">Недостъпни изображения</string>
|
||||
<string name="dialog_message_no_image_support">Приложението на вашия контакт все още не поддържа изпращане на изображения. След като го обновят тази икона ще се промени.</string>
|
||||
<string name="dialog_title_image_support">Вече можете да изпращате изображения на този контакт</string>
|
||||
<string name="dialog_message_image_support">Докоснете иконата за да изпратите изображение</string>
|
||||
<string name="messaging_too_many_attachments_toast">Само първите %dизображения ще бъдат изпратени</string>
|
||||
<string name="message_hint">Напиши съобщение</string>
|
||||
<string name="delete_contact">Изтрий контакт</string>
|
||||
<string name="dialog_title_delete_contact">Потвърди изтриването на контакт</string>
|
||||
<string name="dialog_message_delete_contact">Сигурни ли сте, че искате да изтриете този контакт и всички съобщения, обменени с този контакт?</string>
|
||||
<string name="contact_deleted_toast">Контактът е изтрит</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Добавяне на контакт на живо</string>
|
||||
<string name="face_to_face">Трябва да се срещнете лично с човека, чиито контакт искате да добавите.\n\nТака никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
|
||||
<string name="add_contact_title">Добавяне на контакт</string>
|
||||
<string name="face_to_face">Трябва да се срещнете лично с човека, когото искате да добавите в Контакти.\n\nПо този начин никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
|
||||
<string name="continue_button">Напред</string>
|
||||
<string name="try_again_button">Нов опит</string>
|
||||
<string name="try_again_button">Опитай пак</string>
|
||||
<string name="waiting_for_contact_to_scan">Изчакване контактът да сканира и да се свърже\u2026</string>
|
||||
<string name="exchanging_contact_details">Обмяна на данни за контакт\u2026</string>
|
||||
<string name="contact_added_toast">Добавен контакт: %s</string>
|
||||
<string name="exchanging_contact_details">Обмен на данни за контакт\u2026</string>
|
||||
<string name="contact_added_toast">Добавен конктакт: %s</string>
|
||||
<string name="contact_already_exists">Контактът %s вече съществува</string>
|
||||
<string name="qr_code_invalid">Кодът за QR е недействителен</string>
|
||||
<string name="qr_code_too_old">Сканираният код за QR е от по-ранно издание на %s.\n\nНека вашия контакт инсталира последното издание и да пробва отново.</string>
|
||||
<string name="qr_code_too_new">Сканираният код за QR е от по-ново издание на %s.\n\nИнсталирайте последното издание и пробвайте отново.</string>
|
||||
<string name="camera_error">Грешка в камерата</string>
|
||||
<string name="qr_code_invalid">QR кодът е невалиден</string>
|
||||
<string name="connecting_to_device">Свързване с устройство\u2026</string>
|
||||
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
|
||||
<string name="connection_error_title">Не може да бъде установена връзка с контакта</string>
|
||||
<string name="connection_error_feedback">Ако проблемът продължава, <a href="feedback">изпратете обратна връзка</a>, за да ни помогнете да подобрим приложението.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Добавяне на контакт отдалечено</string>
|
||||
<string name="add_contact_nearby_title">Добавяне на контакт на живо</string>
|
||||
<string name="add_contact_remotely_title">Добавяне на контакт отдалечено</string>
|
||||
<string name="contact_link_intro">Въведете препратката от вашия контакт</string>
|
||||
<string name="contact_link_hint">Препратка от контакт</string>
|
||||
<string name="paste_button">Поставяне</string>
|
||||
<string name="add_contact_button">Добавяне на контакт</string>
|
||||
<string name="copy_button">Копиране</string>
|
||||
<string name="share_button">Споделяне</string>
|
||||
<string name="send_link_title">Размяна на препратки</string>
|
||||
<string name="add_contact_choose_nickname">Избиране на прякор</string>
|
||||
<string name="add_contact_choose_a_nickname">Прякор</string>
|
||||
<string name="nickname_intro">Изберете прякор на контакта. Той е видим само за вас.</string>
|
||||
<string name="your_link">Споделете тази препратка с контакта, когото добавяте</string>
|
||||
<string name="link_clip_label">Препратка на Briar</string>
|
||||
<string name="link_copied_toast">Препратката е копирана</string>
|
||||
<string name="adding_contact_error">Грешка при добавяне на контакт.</string>
|
||||
<string name="pending_contact_requests_snackbar">Има чакащи заявки за контакт</string>
|
||||
<string name="pending_contact_requests">Чакащи заявки за контакт</string>
|
||||
<string name="no_pending_contacts">Няма заявки за контакт</string>
|
||||
<string name="waiting_for_contact_to_come_online">Изчакване на контакта да излезе на линия…</string>
|
||||
<string name="connecting">Свързване…</string>
|
||||
<string name="adding_contact">Добавяне на контакт…</string>
|
||||
<string name="adding_contact_failed">Грешка при добавяне на контакт</string>
|
||||
<string name="dialog_title_remove_pending_contact">Потвърждение на премахване</string>
|
||||
<string name="dialog_message_remove_pending_contact">Контактът е в процес на добавяне. Ако сега го премахнете няма да бъде добавен.</string>
|
||||
<string name="own_link_error">Въведете препратка от ваш контакт, не своята</string>
|
||||
<string name="nickname_missing">Въведете прякор</string>
|
||||
<string name="invalid_link">Препратката е недействителна</string>
|
||||
<string name="unsupported_link">Препратката е от по-ново издание на Briar. Инсталирайте последното издание и пробвайте отново.</string>
|
||||
<string name="intent_own_link">Отваряте своята препратка. Използвайте тази от контакта, когото добавяте.</string>
|
||||
<string name="missing_link">Въведете препратка</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="one">Добавен е нов контакт.</item>
|
||||
<item quantity="other">Добавени са %d нови контакта.</item>
|
||||
</plurals>
|
||||
<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
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">Да</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Не</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s и %s са изпратили еднакви препратки.\n\nЕдиния от двамата вероятно се опитва да разбере кои са контактите ви.\n\nНе им споделяйте, че сте получили същата препратка от друг човек.</string>
|
||||
<string name="pending_contact_updated_toast">Обновена чакаща заявка за контакт</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Запознаване на контакти</string>
|
||||
<string name="introduction_onboarding_text">Можете да запознавате контакти помежду им. Така няма да им се наложи да се срещат лично, за да се свържат в Briar.</string>
|
||||
<string name="introduction_menu_item">Запознаване</string>
|
||||
<string name="introduction_activity_title">Избор на контакт</string>
|
||||
<string name="introduction_not_possible">С тези контакти вече имате запознанство в процес. Нека първо завърши. Ако вие или контактите ви сте рядко на линия може да отнеме известно време.</string>
|
||||
<string name="introduction_message_title">Запознаване на контакти</string>
|
||||
<string name="introduction_onboarding_title">Представете контактите си</string>
|
||||
<string name="introduction_onboarding_text">Можете да представите контактите си един на друг, за да не им се налага да се срещат лично, когато се свързват чрез Briar.</string>
|
||||
<string name="introduction_menu_item">Представи</string>
|
||||
<string name="introduction_activity_title">Избери контакт</string>
|
||||
<string name="introduction_message_title">Представи контакти</string>
|
||||
<string name="introduction_message_hint">Добавете съобщение (незадължително)</string>
|
||||
<string name="introduction_button">Запознаване</string>
|
||||
<string name="introduction_sent">Покана за запознанство е изпратена.</string>
|
||||
<string name="introduction_error">Грешка при изпращане на покана за запознанство.</string>
|
||||
<string name="introduction_request_sent">Пожелахте да запознаете %1$s и %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s пожела да ви запознае с/ъс %2$s. Желаете ли да добавите %2$s към контактите си?</string>
|
||||
<string name="introduction_request_exists_received">%1$s пожела да ви запознае с/ъс %2$s, но %2$s вече е в списъка ви с контакти. Тъй като %1$s може би не знае, все пак можете да отговорите:</string>
|
||||
<string name="introduction_request_answered_received">%1$s пожела да ви запознае с/ъс %2$s.</string>
|
||||
<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>
|
||||
<string name="introduction_button">Представи</string>
|
||||
<string name="introduction_sent">Представянето ви е изпратено.</string>
|
||||
<string name="introduction_error">Възникна грешка при представянето.</string>
|
||||
<string name="introduction_response_error">Грешка при отговор на представянето</string>
|
||||
<string name="introduction_request_sent">Помолихте да представите %1$s на %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s помоли да ви представи %2$s. Искате ли да добавите %2$s към контактите си?</string>
|
||||
<string name="introduction_request_exists_received">%1$s помоли да ви представи на %2$s, но %2$s вече е в списъка ви с контакти. Тъй като %1$s може би не знае, все пак можете да отговорите:</string>
|
||||
<string name="introduction_request_answered_received">%1$s помоли да ви представи на %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Приехте представянето на %1$s.</string>
|
||||
<string name="introduction_response_declined_sent">Отказахте представянето на %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>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="one">Добавен нов контакт.</item>
|
||||
<item quantity="other">%d добавени нови контакти.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">Няма групи</string>
|
||||
<string name="groups_list_empty_action">Докоснете иконата с +, за да създадете своя или поискайте от контактите си да споделят група с вас</string>
|
||||
<string name="groups_created_by">Основател %s</string>
|
||||
<string name="groups_created_by">Създаден от %s</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">%d съобщение</item>
|
||||
<item quantity="other">%d съобщения</item>
|
||||
</plurals>
|
||||
<string name="groups_group_is_empty">Групата е празна</string>
|
||||
<string name="groups_group_is_dissolved">Групата е разпусната</string>
|
||||
<string name="groups_remove">Премахване</string>
|
||||
<string name="groups_create_group_title">Създаване на частна група</string>
|
||||
<string name="groups_create_group_button">Създаване на група</string>
|
||||
<string name="groups_create_group_invitation_button">Изпращане на покана</string>
|
||||
<string name="groups_create_group_hint">Име на частната група</string>
|
||||
<string name="groups_invitation_sent">Поканата за членство в група е изпратена</string>
|
||||
<string name="groups_group_is_dissolved">Групата се е разпаднала</string>
|
||||
<string name="groups_remove">Премахни</string>
|
||||
<string name="groups_create_group_title">Създаване на група</string>
|
||||
<string name="groups_create_group_button">Създай група</string>
|
||||
<string name="groups_create_group_invitation_button">Изпрати покана</string>
|
||||
<string name="groups_create_group_hint">Изберете име за групата</string>
|
||||
<string name="groups_invitation_sent">Поканата в група е изпратена</string>
|
||||
<string name="groups_message_sent">Съобщението е изпратено</string>
|
||||
<string name="groups_member_list">Списък с участници</string>
|
||||
<string name="groups_invite_members">Покани за членство</string>
|
||||
<string name="groups_invite_members">Поканете участници</string>
|
||||
<string name="groups_member_created_you">Вие създадохте групата</string>
|
||||
<string name="groups_member_created">%s създаде групата</string>
|
||||
<string name="groups_member_joined_you">Вие се включихте в групата</string>
|
||||
<string name="groups_member_joined_you">Включихте се в групата</string>
|
||||
<string name="groups_member_joined">%s се включи в групата</string>
|
||||
<string name="groups_leave">Напускане на групата</string>
|
||||
<string name="groups_leave_dialog_title">Потвърждение на напускане</string>
|
||||
<string name="groups_leave">Напусни групата</string>
|
||||
<string name="groups_leave_dialog_title">Потвърждение на напускането</string>
|
||||
<string name="groups_leave_dialog_message">Сигурни ли сте, че искате да напуснете тази група?</string>
|
||||
<string name="groups_dissolve">Разпускане на група</string>
|
||||
<string name="groups_dissolve_dialog_title">Потвърждение на разпускане на група</string>
|
||||
<string name="groups_dissolve_dialog_message">Сигурни ли сте, че искате да разпуснете групата?\n\nОстаналите членове няма да могат да продължат разговорите си и може да не получат последните съобщения.</string>
|
||||
<string name="groups_dissolve_button">Разпускане</string>
|
||||
<string name="groups_dissolved_dialog_title">Разпусната група</string>
|
||||
<string name="groups_dissolved_dialog_message">Групата е разпусната от нейния основател.\n\nНе можете да изпращате съобщения и може да не сте получили всички изпратени до групата съобщения.</string>
|
||||
<string name="groups_dissolve">Затвори групата</string>
|
||||
<string name="groups_dissolve_dialog_title">Потвърди затварянето на групата</string>
|
||||
<string name="groups_dissolve_dialog_message">Сигурни ли сте, че искате да затворите групата?\n\nВсички други участници няма да могат да продължат разговора и може да не получат най-новите съобщения. </string>
|
||||
<string name="groups_dissolve_button">Затвори</string>
|
||||
<string name="groups_dissolved_dialog_title">Групата е затворена</string>
|
||||
<string name="groups_dissolved_dialog_message">Групата е затворена от създателя.\n\nНе можете да пишете нови съобщения в груповия чат и може да не получите най-новите съобщения. </string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">Покани за членство в група</string>
|
||||
<string name="groups_invitations_invitation_sent">Поканихте %1$s в групата „%2$s“</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s ви покани в групата „%2$s“.</string>
|
||||
<string name="groups_invitations_title">Покани в група</string>
|
||||
<string name="groups_invitations_invitation_sent">Поканихте %1$s в групата \"%2$s\"</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s ви покани в групата \"%2$s\".</string>
|
||||
<string name="groups_invitations_joined">Включихте се в групата</string>
|
||||
<string name="groups_invitations_declined">Отказана покана за присъединяване в група</string>
|
||||
<string name="groups_invitations_declined">Отказана покана в група</string>
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">%d получена покана за членство в група</item>
|
||||
<item quantity="other">%d получени покани за членство в група</item>
|
||||
<item quantity="one">%d отворена покана в група</item>
|
||||
<item quantity="other">%d отворени покани в група</item>
|
||||
</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>
|
||||
<string name="groups_invitations_response_accepted_sent">Приехте поканата в група на %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Отказахте поканата в група на %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>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Разкриване на контакти</string>
|
||||
<string name="groups_reveal_dialog_message">Можете да изберете дали да разкриете контактите на всички сегашни и бъдещи членове на групата.\n\nС разкриването им връзката с групата става по-бърза и надеждна, защото общувате с разкритите контакти, даже и основателят на групата да е извън мрежа.</string>
|
||||
<string name="groups_reveal_visible">Отношенията ви с контакта са видими за групата</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Отношенията ви с контакта са видими за групата (разкрити от вас)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Отношенията ви с контакта са видими за групата (разкрити от %s)</string>
|
||||
<string name="groups_reveal_invisible">Отношенията ви с контакта не са видими за групата</string>
|
||||
<string name="groups_reveal_contacts">Разкрий контакти</string>
|
||||
<string name="groups_reveal_dialog_message">Може да изберете да разкриете контактите на всички сегашни и бъдещи участници в тази група.\n\nРазкриването на контактите прави връзката с групата по-бърза и сигурна, тъй като можете да общувате с разкрити контакти, дори когато създателят на групата е офлайн.</string>
|
||||
<string name="groups_reveal_visible">Връзка с контакта се вижда от групата</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Връзка с контакта се вижда от групата (разкрита от вас)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Връзка с контакта се вижда от групата (разкрита от %s)</string>
|
||||
<string name="groups_reveal_invisible">Връзка с контакта не се вижда от групата</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Няма форуми</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>
|
||||
<string name="choose_forum_hint">Изберете име за форума</string>
|
||||
<string name="create_forum_button">Създай форум</string>
|
||||
<string name="forum_created_toast">Форумът е създаден</string>
|
||||
<string name="no_forum_posts">Няма публикации</string>
|
||||
<string name="no_posts">Няма публикации</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d публикация</item>
|
||||
<item quantity="other">%d публикации</item>
|
||||
</plurals>
|
||||
<string name="forum_new_message_hint">Публикация</string>
|
||||
<string name="forum_message_reply_hint">Отговор</string>
|
||||
<string name="btn_reply">Отговаряне</string>
|
||||
<string name="forum_leave">Напускане на форума</string>
|
||||
<string name="dialog_title_leave_forum">Потвърждение на напускане на форум</string>
|
||||
<string name="dialog_message_leave_forum">Сигурни ли сте, че искате да напуснете този форум?\n\nКонтактите, с които сте го споделили може да спрат да получават публикации.</string>
|
||||
<string name="dialog_button_leave">Напускане</string>
|
||||
<string name="forum_left_toast">Напуснахте форума</string>
|
||||
<string name="forum_message_reply_hint">Нов отговор</string>
|
||||
<string name="btn_reply">Отговори</string>
|
||||
<string name="forum_leave">Напусни форума</string>
|
||||
<string name="dialog_title_leave_forum">Потвърдете напускането на форума</string>
|
||||
<string name="dialog_button_leave">Напусни</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Споделяне форума</string>
|
||||
<string name="forum_share_button">Сподели форум</string>
|
||||
<string name="contacts_selected">Избрани контакти</string>
|
||||
<string name="activity_share_toolbar_header">Избиране на контакти</string>
|
||||
<string name="no_contacts_selector">Няма контакти</string>
|
||||
<string name="no_contacts_selector_action">Върнете се след като добавите контакти</string>
|
||||
<string name="forum_shared_snackbar">Форумът е споделен с избраните контакти</string>
|
||||
<string name="forum_share_message">Добавете съобщение (незадължително)</string>
|
||||
<string name="forum_share_error">Грешка при споделянето на форума.</string>
|
||||
<string name="forum_invitation_received">%1$s сподели с вас форума „%2$s“.</string>
|
||||
<string name="forum_invitation_sent">Споделихте форума „%1$s“ с/ъс %2$s.</string>
|
||||
<string name="forum_invitations_title">Покани за членство във форум</string>
|
||||
<string name="forum_invitation_exists">Вече приехте покана за този форум.\n\nС приемане на повече покани връзката с групата става по-бърза и надеждна.</string>
|
||||
<string name="forum_joined_toast">Присъединихте се към форума</string>
|
||||
<string name="forum_declined_toast">Поканата е отказана</string>
|
||||
<string name="forum_share_error">Възникна грешка при споделянето на този форум.</string>
|
||||
<string name="forum_invitation_received">%1$s сподели форума \"%2$s\" с вас.</string>
|
||||
<string name="forum_invitation_sent">Споделихте форума \"%1$s\" с %2$s.</string>
|
||||
<string name="forum_invitations_title">Покани във форум</string>
|
||||
<string name="shared_by_format">Споделен от %s</string>
|
||||
<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_sent">Приехте поканата за форум на %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Отказахте поканата във форум от %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>
|
||||
<string name="sharing_status">Статус на споделянето</string>
|
||||
<string name="sharing_status_forum">Всеки участник във форума може да го сподели с контактите си. Споделяте този форум със следните контакти. Възможно е да има и други, които не можете да видите.</string>
|
||||
<string name="shared_with">Споделен %1$d (на линия %2$d)</string>
|
||||
<string name="shared_with">Споделен с %1$d (%2$d онлайн)</string>
|
||||
<plurals name="forums_shared">
|
||||
<item quantity="one">%d форум, споделен от контакти</item>
|
||||
<item quantity="other">%d форума, споделени от контакти</item>
|
||||
</plurals>
|
||||
<string name="nobody">Никого</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Няма публикации</string>
|
||||
<string name="read_more">повече</string>
|
||||
<string name="blogs_write_blog_post">Нова публикация в блога</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Въведете своята публикация</string>
|
||||
<string name="read_more">прочети още</string>
|
||||
<string name="blogs_write_blog_post">Нова блог публикация</string>
|
||||
<string name="blogs_publish_blog_post">Публикуване</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>
|
||||
<string name="blogs_feed_empty_state_action">Публикации от вашите контакти и абонираните блогове се показват тук.\n\nДокоснете иконата на писалка, за да направите публикация.</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_remove_blog">Премахване на блог</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Сигурни ли сте, че желаете да изтриете блога?\n\nПубликациите ще бъдат премахнати от устройството ви, но не и от устройствата на другите членове.\n\nКонтактите, с които сте споделили този блог може да спрат да получават обновявания.</string>
|
||||
<string name="blogs_remove_blog_ok">Премахване</string>
|
||||
<string name="blogs_blog_removed">Блогът е премахнат</string>
|
||||
<string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string>
|
||||
<string name="blogs_reblog_button">Препубликуване</string>
|
||||
<string name="blogs_reblog_button">Реблог</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Споделяне на блог</string>
|
||||
<string name="blogs_sharing_error">Грешка при споделяне блога.</string>
|
||||
<string name="blogs_sharing_button">Споделяне на блог</string>
|
||||
<string name="blogs_sharing_error">Възникна грешка при споделянето на този блог.</string>
|
||||
<string name="blogs_sharing_button">Сподели блог</string>
|
||||
<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>
|
||||
<string name="blogs_sharing_invitation_sent">Споделихте блога „%1$s“ с/ъс %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Покани за абонамент за блог</string>
|
||||
<string name="blogs_sharing_joined_toast">Абонирахте се за блогa</string>
|
||||
<string name="blogs_sharing_declined_toast">Поканата е отказана</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Приехте поканата в блог от %s.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Отказахте поканата в блог от %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>
|
||||
<string name="blogs_sharing_invitation_sent">Споделихте блога \"%1$s\" с %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Блог покани</string>
|
||||
<string name="sharing_status_blog">Всеки абонат на блога може да го сподели с контактите си. Споделяте този блог със следните контакти. Възможно е да има и други, които не можете да видите.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Внасяне на емисия на RSS</string>
|
||||
<string name="blogs_rss_feeds_import">Внасяне на RSS емисия</string>
|
||||
<string name="blogs_rss_feeds_import_button">Внасяне</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Aдрес на емисия</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_import_hint">Въведете URL адреса на RSS емисията</string>
|
||||
<string name="blogs_rss_feeds_import_error">Възникна грешка при внасянето на емисия.</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_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_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">Език и регион</string>
|
||||
<string name="pref_language_changed">Тази настройка ще име ефект след рестарт на Briar. Отпишете се и рестартирайте Briar.</string>
|
||||
<string name="pref_language_default">Спрямо системата</string>
|
||||
<string name="display_settings_title">Външен вид</string>
|
||||
<string name="pref_theme_title">Тема</string>
|
||||
<string name="pref_theme_light">Светла</string>
|
||||
<string name="pref_theme_dark">Тъмна</string>
|
||||
<string name="pref_theme_auto">Автоматична (ден или нощ)</string>
|
||||
<string name="pref_theme_system">Спрямо системата</string>
|
||||
<!--Settings Connections-->
|
||||
<string name="network_settings_title">Свързаност</string>
|
||||
<string name="bluetooth_setting">Свързване с контактите чрез Bluetooth</string>
|
||||
<string name="wifi_setting">Свързване с контактите в същата безжична мрежа</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 Network-->
|
||||
<string name="network_settings_title">Мрежа</string>
|
||||
<string name="bluetooth_setting">Свързване чрез Bluetooth</string>
|
||||
<string name="bluetooth_setting_enabled">Когато контактите са наблизо</string>
|
||||
<string name="bluetooth_setting_disabled">Само при добавяне на контакти</string>
|
||||
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Сигурност</string>
|
||||
<string name="pref_lock_title">Заключване на приложението</string>
|
||||
<string name="pref_lock_summary">Заключва се екрана, за да предпази Briar докато сте вписани</string>
|
||||
<string name="pref_lock_disabled_summary">За да се възползвате от тази възможност, настройте заключване на екрана</string>
|
||||
<string name="pref_lock_timeout_title">Заключване при бездействие</string>
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<string name="pref_lock_timeout_summary">Briar се изключва автоматично при неактивност от %s</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_1">1 минута</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_5">5 минути</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_15">15 минути</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_30">30 минути</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_60">1 час</string>
|
||||
<string name="pref_lock_timeout_never">Никога</string>
|
||||
<string name="pref_lock_timeout_never_summary">Briar никога да не се заключва автоматично</string>
|
||||
<string name="change_password">Промяна на парола</string>
|
||||
<string name="current_password">Текуща парола</string>
|
||||
<string name="choose_new_password">Нова парола</string>
|
||||
<string name="confirm_new_password">Потвърдете новата парола</string>
|
||||
<string name="password_changed">Паролата е променена.</string>
|
||||
<string name="panic_setting">Настройка на бутон за паника</string>
|
||||
<string name="panic_setting_title">Бутон за паника</string>
|
||||
<string name="panic_setting_hint">Настройва се реакцията на Briar при използване на приложение за бутон за паника</string>
|
||||
<string name="panic_app_setting_title">Приложение бутон за паника</string>
|
||||
<string name="panic_setting">Настройка на паник бутон</string>
|
||||
<string name="panic_setting_title">Паник бутон</string>
|
||||
<string name="panic_setting_hint">Конфигурация на приложение за паник бутон</string>
|
||||
<string name="panic_app_setting_title">Приложение за паник бутон</string>
|
||||
<string name="unknown_app">непознато приложение</string>
|
||||
<string name="panic_app_setting_summary">Няма зададено приложение</string>
|
||||
<string name="panic_app_setting_none">Няма</string>
|
||||
<string name="dialog_title_connect_panic_app">Потвърждение на приложение при паника</string>
|
||||
<string name="dialog_message_connect_panic_app">Сигурни ли сте, че желаете да позволите на %1$s да задейства разрушителните действия на бутона за паника?</string>
|
||||
<string name="panic_setting_destructive_action">Разрушителни действия</string>
|
||||
<string name="dialog_title_connect_panic_app">Потвърждение на паник приложение</string>
|
||||
<string name="dialog_message_connect_panic_app">Сигурни ли сте, че искате да позволите на %1$s да задейства унищожителни действия на паник бутон?</string>
|
||||
<string name="panic_setting_signout_title">Отписване</string>
|
||||
<string name="panic_setting_signout_summary">Отписване от Briar при натиснат бутон за паника</string>
|
||||
<string name="panic_setting_signout_summary">Отписване от Briar, ако паник бутонът е натиснат</string>
|
||||
<string name="purge_setting_title">Изтриване на профил</string>
|
||||
<string name="purge_setting_summary">Профила на Briar се изтрива при натиснат бутон за паника. Внимание: Изтрива безвъзвратно профила, контактите и съобщенията</string>
|
||||
<string name="purge_setting_summary">Изтриване на Briar профила, ако паник бутонът е натиснат. Внимание: Изтрива завинаги вашия профил, контакти и съобщения</string>
|
||||
<string name="uninstall_setting_title">Деинсталиране на Briar</string>
|
||||
<string name="uninstall_setting_summary">Изисква ръчно потвърждение в паник случай</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Известия</string>
|
||||
<string name="notify_sign_in_title">Напомняне за вписване</string>
|
||||
<string name="notify_sign_in_summary">Известие след рестарт на устройството или обновяване на приложението</string>
|
||||
<string name="notify_private_messages_setting_title">Лични съобщения</string>
|
||||
<string name="notify_private_messages_setting_summary">Известия за лични съобщения</string>
|
||||
<string name="notify_private_messages_setting_summary_26">Настройки на известия за лични съобщения</string>
|
||||
<string name="notify_private_messages_setting_summary">Показвай известия за лични съобщения</string>
|
||||
<string name="notify_group_messages_setting_title">Групови съобщения</string>
|
||||
<string name="notify_group_messages_setting_summary">Известия за групови съобщения</string>
|
||||
<string name="notify_group_messages_setting_summary_26">Настройки на известия за групови съобщения</string>
|
||||
<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_summary">Известия за публикации в блог</string>
|
||||
<string name="notify_blog_posts_setting_summary_26">Настройки на известия за публикации в блог</string>
|
||||
<string name="notify_group_messages_setting_summary">Показвай известия за групови съобщения</string>
|
||||
<string name="notify_forum_posts_setting_title">Форумни публикации</string>
|
||||
<string name="notify_forum_posts_setting_summary">Показвай известия за форумни публикации</string>
|
||||
<string name="notify_blog_posts_setting_title">Блог публикации</string>
|
||||
<string name="notify_blog_posts_setting_summary">Показвай известия за блог публикации</string>
|
||||
<string name="notify_vibration_setting">Вибрация</string>
|
||||
<string name="notify_lock_screen_setting_title">Заключен екран</string>
|
||||
<string name="notify_lock_screen_setting_summary">Показвай известия за на заключен екран</string>
|
||||
<string name="notify_sound_setting">Звук</string>
|
||||
<string name="notify_sound_setting_default">Подразбиран тон на звънене</string>
|
||||
<string name="notify_sound_setting_disabled">Няма</string>
|
||||
<string name="choose_ringtone_title">Избор на тон за звънене</string>
|
||||
<string name="cannot_load_ringtone">Тонът за звънене не може да бъде зареден</string>
|
||||
<!--Conversation Settings-->
|
||||
<string name="disappearing_messages_title">Изчезващи съобщения</string>
|
||||
<string name="disappearing_messages_explanation_long">При включване, тази настройка прави бъдещите съобщения в този разговор да изчезват след 7\u00A0дни.
|
||||
\n\nОтброяването при изпращача започва след като съобщението е било доставено, а при получателя – след като го е прочел.
|
||||
\n\nСъобщенията, които ще изчезнат са отбелязани с бомба.
|
||||
\n\nИмайте предвид, че получателите могат да правят копия на получените от вас съобщения.
|
||||
\n\nАко вие промените настройката промяната ще влезе в действие веднага, още върху следващото ви съобщение, а при вашите контакти след получаването му. Контактите ви също могат да правят промяна на тази настройка, което ще се отрази и на двама ви.</string>
|
||||
<string name="learn_more">Научете повече</string>
|
||||
<string name="disappearing_messages_summary">Бъдещите съобщения в разговора изчезват след 7\u00A0дни</string>
|
||||
<string name="notify_sound_setting_default">Мелодия по подразбиране</string>
|
||||
<string name="notify_sound_setting_disabled">Никакви</string>
|
||||
<string name="choose_ringtone_title">Изберете рингтон</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="send_feedback">Изпращане на отзив</string>
|
||||
<string name="feedback_settings_title">Отзиви</string>
|
||||
<string name="send_feedback">Изпращане на отзиви</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Предупреждение за препратка</string>
|
||||
<string name="link_warning_intro">Препратката ще бъде отворена от външно приложение.</string>
|
||||
<string name="link_warning_text">Така отворена може да бъде използвана, за да бъдете идентифицирани. Преценете дали имате доверие на подателя и обмислете дали да не я отворите с Tor Browser.</string>
|
||||
<string name="link_warning_open_link">Отваряне</string>
|
||||
<string name="link_warning_title">Предупреждение за линк</string>
|
||||
<string name="link_warning_intro">Линкът ще се отвори във външно приложение.</string>
|
||||
<string name="link_warning_text">Линкът може да се използва, за да ви идентифицира. Помислете дали имате доверие на човека, който ви изпраща линка, и обмислете дали да не го отворите с Orfox.</string>
|
||||
<string name="link_warning_open_link">Отвори линк</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Доклад на срив</string>
|
||||
<string name="briar_crashed">Извинете, Briar се срина.</string>
|
||||
<string name="not_your_fault">Не е по ваша вина.</string>
|
||||
<string name="please_send_report">Помогнете да направим Briar по-добър като ни изпратите доклад.</string>
|
||||
<string name="report_is_encrypted">Даваме обещание, че докладът е шифрован и е изпратен добре защитен.</string>
|
||||
<string name="feedback_title">Обратна връзка</string>
|
||||
<string name="describe_crash">Опишете случилото се (незадължително)</string>
|
||||
<string name="enter_feedback">Въведете обратна връзка</string>
|
||||
<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_build_config">Настройка на изданието</string>
|
||||
<string name="dev_report_logcat">Журнал на приложението</string>
|
||||
<string name="dev_report_device_features">Характеристики</string>
|
||||
<string name="please_send_report">Моля, помогнете да изградим по-добър Briar, като ни изпратите доклад.</string>
|
||||
<string name="report_is_encrypted">Гарантираме, че докладът е криптиран и изпратен безопасно.</string>
|
||||
<string name="feedback_title">Отзиви</string>
|
||||
<string name="describe_crash">Опишете станалото (незадължително)</string>
|
||||
<string name="enter_feedback">Въведете отзив</string>
|
||||
<string name="optional_contact_email">Имейл адресът ви (незадължително)</string>
|
||||
<string name="include_debug_report_crash">Добави анонимни данни за срива</string>
|
||||
<string name="include_debug_report_feedback">Добави анонимни данни за това устройствo</string>
|
||||
<string name="could_not_load_report_data">Данните за доклада не можаха да заредят.</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>
|
||||
<string name="dev_report_error">Грешка при изпращане на доклад</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Отписване от Briar…</string>
|
||||
<string name="progress_title_logout">Отписване от Briar...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Открито е приложение отгоре</string>
|
||||
<string name="screen_filter_body">Друго приложение се изчертава върху 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">Разрешаване на тези приложения да се изчертават отгоре</string>
|
||||
<string name="screen_filter_review_apps">Преглеждане</string>
|
||||
<string name="screen_filter_title">Открит е овърлей на екрана</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Разрешение за камера</string>
|
||||
<string name="permission_camera_request_body">За да сканира кода за QR, Briar трябва да използва камерата.</string>
|
||||
<string name="permission_location_title">Разрешение за местоположение</string>
|
||||
<string name="permission_location_request_body">За да открива устройства чрез Bluetooth, на Briar му е необходимо разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
|
||||
<string name="permission_camera_location_title">Камера и местоположение</string>
|
||||
<string name="permission_camera_location_request_body">За да сканира кодове за QR, на Briar му е необходимо разрешение за достъп до камерата.\n\nЗа да открива устройства чрез Bluetooth, на Briar му е необходимо разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
|
||||
<string name="permission_camera_denied_body">Отказахте достъп до камерата, но тя е необходима за добавянето на контакти.\n\nОбмислете дали да не дадете разрешение.</string>
|
||||
<string name="permission_location_denied_body">Отказахте достъп до местоположението, но то е необходимо за откриване на устройства чрез Bluetooth.\n\nОбмислете дали да не дадете разрешение.</string>
|
||||
<string name="permission_location_setting_title">Настройки на местоположението</string>
|
||||
<string name="permission_location_setting_body">Местоположението на устройството ви трябва да е включено, за да бъдат откривани устройства чрез Bluetooth. За да продължите включете местоположението. След това можете да го изключите.</string>
|
||||
<string name="permission_location_setting_button">Включване на местеположение</string>
|
||||
<string name="qr_code">Код за QR</string>
|
||||
<string name="show_qr_code_fullscreen">Код за QR на цял екран</string>
|
||||
<!--App Locking-->
|
||||
<string name="lock_unlock">Отключете Briar</string>
|
||||
<string name="lock_unlock_verbose">Въведете своя PIN, фигура или парола</string>
|
||||
<string name="lock_unlock_fingerprint_description">Докоснете сензора за отпечатъци с регистрирания пръст</string>
|
||||
<string name="lock_unlock_password">Използване на парола</string>
|
||||
<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>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_bob">Боби</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_carol">Васко</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
||||
<string name="screenshot_message_1">Здравей, Боби!</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
||||
<string name="screenshot_message_2">Здравей, Ани! Благодаря ти, че ми каза за Briar!</string>
|
||||
<!--This is a message to be used in screenshots.-->
|
||||
<string name="screenshot_message_3">Радвам се, че ти харесва! И ти би направил същото 😀</string>
|
||||
</resources>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<string name="confirm_password">Potwierdź hasło</string>
|
||||
<string name="name_too_long">Nazwa użytkownika jest zbyt długa</string>
|
||||
<string name="password_too_weak">Hasło jest zbyt długie</string>
|
||||
<string name="passwords_do_not_match">Hasła różnią się</string>
|
||||
<string name="passwords_do_not_match">Hasła się nie zgadzają</string>
|
||||
<string name="create_account_button">Utwórz Konto</string>
|
||||
<string name="more_info">Więcej Informacji</string>
|
||||
<string name="don_t_ask_again">Nie pytaj ponownie</string>
|
||||
@@ -27,11 +27,10 @@
|
||||
<!--Login-->
|
||||
<string name="enter_password">Hasło</string>
|
||||
<string name="try_again">Złe hasło, spróbuj ponownie</string>
|
||||
<string name="dialog_title_cannot_check_password">Nie można zweryfikować hasła</string>
|
||||
<string name="sign_in_button">Zaloguj Się</string>
|
||||
<string name="forgotten_password">Zapomniałem hasło</string>
|
||||
<string name="dialog_title_lost_password">Nie pamiętam hasła</string>
|
||||
<string name="dialog_message_lost_password">Twoje konto Briar jest zaszyfrowane na Twoim urządzeniu, nie w chmurze, więc nie będzie można zresetować Twojego hasła. Czy chcesz usunąć swoje konto i stworzyć nowe?\n\nUwaga: Twoje hasła, kontakty i wiadomości będą utracone.</string>
|
||||
<string name="dialog_message_lost_password">Twoje konto Briar jest zaszyfrowane na Twoim urządzeniu nie w chmurze, więc nie będzie można zresetować Twojego hasła. Czy chcesz usunąć swoje konto i stworzyć nowe?\n\nUwaga: Twoje hasła, kontakty i wiadomości będą utracone.</string>
|
||||
<string name="startup_failed_notification_title">Briar nie mógł się uruchomić</string>
|
||||
<string name="startup_failed_notification_text">Naciśnij, aby uzyskać więcej informacji</string>
|
||||
<string name="startup_failed_activity_title">Briar nie mógł się uruchomić</string>
|
||||
@@ -46,9 +45,7 @@
|
||||
<item quantity="other">To jest testowa wersja Briar. Twoje konto wygaśnie za %d dni i nie będzie odnowione.</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">Ten program wygasł.\nDziękujemy za testy!</string>
|
||||
<string name="download_briar">Aby móc nadal korzystać z Briar, pobierz najnowszą wersję.</string>
|
||||
<string name="create_new_account">Musisz utworzyć nowe konto, ale możesz użyć takiej samej nazwy użytkownika.</string>
|
||||
<string name="download_briar_button">Pobierz najnowszą wersję</string>
|
||||
<string name="startup_open_database">Deszyfruję Bazę Danych...</string>
|
||||
<string name="startup_migrate_database">Aktualizuję Bazę Danych...</string>
|
||||
<string name="startup_compact_database">Kompaktowanie Bazy Danych…</string>
|
||||
@@ -63,16 +60,12 @@
|
||||
<string name="lock_button">Zablokuj Aplikację</string>
|
||||
<string name="settings_button">Ustawienia</string>
|
||||
<string name="sign_out_button">Wyloguj się</string>
|
||||
<string name="transports_onboarding_text">Dotknij tutaj aby zmienić w jaki sposób Briar łączy się z twoimi kontaktami.</string>
|
||||
<!--Transports: Tor-->
|
||||
<string name="transport_tor">Internet</string>
|
||||
<string name="tor_plugin_status_inactive">Briar nie może połączyć się z Internetem</string>
|
||||
<!--Transports: Wi-Fi-->
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<!--Transports: Bluetooth-->
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="bt_device_status_on">Bluetooth jest włączony</string>
|
||||
<string name="bt_device_status_off">Bluetooth jest wyłączony</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">Wylogowano z Briar</string>
|
||||
<string name="reminder_notification_text">Dotknij, aby zalogować się ponownie</string>
|
||||
@@ -135,7 +128,6 @@
|
||||
<string name="date_no_private_messages">Brak wiadomości.</string>
|
||||
<string name="no_private_messages">Brak wiadomości do pokazania</string>
|
||||
<string name="message_hint">Nowa wiadomość</string>
|
||||
<string name="message_hint_auto_delete">Nowa znikająca wiadomość</string>
|
||||
<string name="image_caption_hint">Dodaj podpis (opcjonalne)</string>
|
||||
<string name="image_attach">Załącz obraz</string>
|
||||
<string name="image_attach_error">Nie udało się dołączyć obrazu(ów)</string>
|
||||
@@ -143,18 +135,12 @@
|
||||
<string name="image_attach_error_invalid_mime_type">Format obrazu jest nieobsługiwany: %s</string>
|
||||
<string name="set_contact_alias">Zmień nazwę kontaktu</string>
|
||||
<string name="set_contact_alias_hint">Nazwa kontaktu</string>
|
||||
<string name="menu_item_disappearing_messages">Znikające wiadomości</string>
|
||||
<string name="menu_item_connect_via_bluetooth">Połącz przez Bluetooth</string>
|
||||
<string name="dialog_title_connect_via_bluetooth">Połącz przez 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."-->
|
||||
<!--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">Dotknij tutaj aby dowiedzieć się więcej.</string>
|
||||
<string name="auto_delete_changed_warning_send">Wyślij mimo to</string>
|
||||
<string name="delete_all_messages">Usuń wszystkie wiadomości</string>
|
||||
<string name="dialog_title_delete_all_messages">Potwierdź usunięcie wiadomości</string>
|
||||
<string name="dialog_message_delete_all_messages">Na pewno chcesz usunąć wszystkie wiadomości?</string>
|
||||
<string name="dialog_title_not_all_messages_deleted">Nie mogłem usunąć wszystkich wiadomości</string>
|
||||
<string name="delete_contact">Usuń kontakt</string>
|
||||
<string name="dialog_title_delete_contact">Potwierdź usunięcie kontaktu</string>
|
||||
@@ -187,7 +173,7 @@
|
||||
<string name="connecting_to_device">Łączenie z urządzeniem\u2026</string>
|
||||
<string name="authenticating_with_device">Autoryzowanie z urządzeniem\u2026</string>
|
||||
<string name="connection_error_title">Nie udało się połączyć z kontaktem</string>
|
||||
<string name="connection_error_feedback">Jeśli problem będzie występować dalej, proszę <a href="feedback">wysłać opinię</a> aby pomóc nam ulepszyć aplikację.</string>
|
||||
<string name="connection_error_feedback">Jeśli problem będzie występować dalej, proszę <a href="feedback">wysłać zgłoszenie</a> aby pomóc nam ulepszyć aplikację.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Dodaj Kontakt na odległość</string>
|
||||
<string name="add_contact_nearby_title">Dodaj kontakt w pobliżu</string>
|
||||
@@ -415,8 +401,6 @@
|
||||
<string name="blogs_rss_feeds_import_button">Zaimportuj</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Wprowadź adres URL do kanału RSS</string>
|
||||
<string name="blogs_rss_feeds_import_error">Przepraszamy! Wystąpił błąd podczas importowania twojego kanału RSS</string>
|
||||
<string name="blogs_rss_feeds_import_exists">Ten feed został już zaimportowany.</string>
|
||||
<string name="blogs_rss_feeds">Feedy RSS</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Zaimportowane:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Ostatnio Zaktualizowane:</string>
|
||||
@@ -426,7 +410,6 @@
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Brak RSS do wyświetlenia\n\nDotknij ikonki + aby zaimportować kanał.</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Wystąpił problem podczas ładowania twoich kanałów RSS. Proszę spróbować ponownie później.</string>
|
||||
<!--Settings Profile Picture-->
|
||||
<string name="dialog_confirm_profile_picture_title">Zmień zdjęcie profilowe</string>
|
||||
<!--Settings Display-->
|
||||
<string name="pref_language_title">Język & region</string>
|
||||
<string name="pref_language_changed">Te ustawienia zostaną zastosowane gdy zrestartujesz Briar. Proszę się wylogować i zrestartować Briar.</string>
|
||||
@@ -439,7 +422,6 @@
|
||||
<string name="pref_theme_system">Domyślny systemu</string>
|
||||
<!--Settings Connections-->
|
||||
<string name="network_settings_title">Połączenia</string>
|
||||
<string name="tor_enable_summary">Dla zapewnienia prywatności, wszystkie połączenia przechodzą przez sieć Tor</string>
|
||||
<string name="tor_network_setting_automatic">Automatycznie bazując na lokalizacji</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">Automatycznie: %1$s (za %2$s)</string>
|
||||
@@ -507,7 +489,6 @@
|
||||
<string name="choose_ringtone_title">Wybierz dzwonek</string>
|
||||
<string name="cannot_load_ringtone">Nie mogę załadować dzwonka</string>
|
||||
<!--Conversation Settings-->
|
||||
<string name="disappearing_messages_title">Znikające wiadomości</string>
|
||||
<string name="learn_more">Dowiedz się więcej</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="send_feedback">Wyślij opinię</string>
|
||||
@@ -524,7 +505,7 @@
|
||||
<string name="report_is_encrypted">Obiecujemy, że raport z awarii jest zaszyfrowany i wysyłany bezpiecznie.</string>
|
||||
<string name="feedback_title">Twoja opinia</string>
|
||||
<string name="describe_crash">Opisz co się stało (opcjonalne)</string>
|
||||
<string name="enter_feedback">Napisz swoją opinię</string>
|
||||
<string name="enter_feedback">Wprowadź swoje uwagi</string>
|
||||
<string name="optional_contact_email">Twój adres email (opcjonalne)</string>
|
||||
<string name="include_debug_report_crash">Załącz anonimowe dane na temat awarii</string>
|
||||
<string name="include_debug_report_feedback">Załącz anonimowe dane o tym urządzeniu</string>
|
||||
@@ -534,8 +515,6 @@
|
||||
<string name="dev_report_connectivity">Łączenie</string>
|
||||
<string name="send_report">Wyślij raport</string>
|
||||
<string name="close">Zamknij</string>
|
||||
<string name="dev_report_sending">Wysyłanie opinii...</string>
|
||||
<string name="dev_report_sent">Wysłano opinię</string>
|
||||
<string name="dev_report_saved">Raport zapisany. Zostanie wysłany następnym razem kiedy zalogujesz się do Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Wylogowywanie z Briar...</string>
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
<dimen name="margin_xlarge">32dp</dimen>
|
||||
<dimen name="margin_xxlarge">64dp</dimen>
|
||||
|
||||
<!-- hero icons -->
|
||||
<dimen name="hero_square">128dp</dimen>
|
||||
<dimen name="hero_rect_width">192dp</dimen>
|
||||
<dimen name="hero_rect_height">96dp</dimen>
|
||||
|
||||
<!-- v2 dimens -->
|
||||
<dimen name="text_size_tiny">12sp</dimen>
|
||||
<dimen name="text_size_small">14sp</dimen>
|
||||
|
||||
@@ -46,12 +46,14 @@
|
||||
<string name="forgotten_password">I have forgotten my password</string>
|
||||
<string name="dialog_title_lost_password">Lost Password</string>
|
||||
<string name="dialog_message_lost_password">Your Briar account is stored encrypted on your device, not in the cloud, so we can\'t reset your password. Would you like to delete your account and start again?\n\nCaution: Your identities, contacts and messages will be permanently lost.</string>
|
||||
<string name="startup_failed_notification_title">Briar could not start</string>
|
||||
<string name="startup_failed_notification_text">Tap for more information.</string>
|
||||
<string name="startup_failed_activity_title">Briar Startup Failure</string>
|
||||
<string name="startup_failed_clock_error">Briar was unable to start because your device\'s clock is wrong.\n\nPlease set your device\'s clock to the right time and try again.</string>
|
||||
<string name="startup_failed_db_error">Briar was unable to open the database containing your account, your contacts and your messages.\n\nPlease upgrade to the latest version of the app and try again, or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
|
||||
<string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version.\n\nYou must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
|
||||
<string name="startup_failed_data_too_new_error">Your account was created with a newer version of this app and cannot be opened with this version.\n\nPlease upgrade to the latest version and try again.</string>
|
||||
<string name="startup_failed_service_error">Briar was unable to start a required component.\n\nPlease upgrade to the latest version of the app and try again.</string>
|
||||
<string name="startup_failed_clock_error">Briar was unable to start because your device\'s clock is wrong. Please set your device\'s clock to the right time and try again.</string>
|
||||
<string name="startup_failed_db_error">For some reason, your Briar database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
|
||||
<string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
|
||||
<string name="startup_failed_data_too_new_error">This version of the app is too old. Please upgrade to the latest version and try again.</string>
|
||||
<string name="startup_failed_service_error">Briar was unable to start a required plugin. Reinstalling Briar usually solves this problem. However, please note that you will then lose your account and all data associated with it since Briar is not using central servers to store your data on.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">This is a test version of Briar. Your account will expire in %d day and cannot be renewed.</item>
|
||||
<item quantity="other">This is a test version of Briar. Your account will expire in %d days and cannot be renewed.</item>
|
||||
@@ -628,7 +630,7 @@
|
||||
|
||||
<!-- Crash Reporter -->
|
||||
<string name="crash_report_title">Briar Crash Report</string>
|
||||
<string name="briar_crashed">Sorry, Briar has crashed</string>
|
||||
<string name="briar_crashed">Sorry, Briar has crashed.</string>
|
||||
<string name="not_your_fault">This is not your fault.</string>
|
||||
<string name="please_send_report">Please help us build a better Briar by sending us a crash report.</string>
|
||||
<string name="report_is_encrypted">We promise that the report is encrypted and sent securely.</string>
|
||||
@@ -692,77 +694,6 @@
|
||||
<!-- Connections Screen -->
|
||||
<string name="transports_help_text">Briar can connect to your contacts via the Internet, Wi-Fi or Bluetooth.\n\nAll Internet connections go through the Tor network for privacy.\n\nIf a contact can be reached by multiple methods, Briar uses them in parallel.</string>
|
||||
|
||||
<!-- Share app offline -->
|
||||
<string name="hotspot_title">Share this app offline</string>
|
||||
<string name="hotspot_intro">Share this app with someone nearby without Internet connection by using your phone\'s Wi-Fi.
|
||||
\n\nYour phone will start a Wi-Fi hotspot. People nearby can connect to the hotspot and download the Briar app from your phone.</string>
|
||||
<string name="hotspot_button_start_sharing">Start hotspot</string>
|
||||
<string name="hotspot_button_stop_sharing">Stop hotspot</string>
|
||||
<string name="hotspot_progress_text_start">Setting up hotspot…</string>
|
||||
<string name="hotspot_notification_channel_title">Wi-Fi hotspot</string>
|
||||
<string name="hotspot_notification_title">Sharing Briar offline</string>
|
||||
<string name="hotspot_button_connected">Next</string>
|
||||
|
||||
<string name="permission_hotspot_location_request_body">To create a Wi-Fi hotspot, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string>
|
||||
<string name="permission_hotspot_location_denied_body">You have denied access to your location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access.</string>
|
||||
<string name="wifi_settings_title">Wi-Fi setting</string>
|
||||
<string name="wifi_settings_request_enable_body">To create a Wi-Fi hotspot, Briar needs to use Wi-Fi. Please enable it.</string>
|
||||
<string name="wifi_settings_request_denied_body">You have denied permission to enable Wi-Fi, but Briar needs to use Wi-Fi.\n\nPlease consider enabling it.</string>
|
||||
|
||||
<string name="hotspot_tab_manual">Manual</string>
|
||||
<!-- The placeholder to be inserted into the string 'hotspot_manual_wifi': People can connect by %s -->
|
||||
<string name="hotspot_scanning_a_qr_code">scanning a QR code</string>
|
||||
|
||||
<!-- Wi-Fi setup -->
|
||||
<!-- The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code' -->
|
||||
<string name="hotspot_manual_wifi">Your phone is providing a Wi-Fi hotspot. People who want to download Briar can connect to the hotspot by adding it in their device\'s Wi-Fi settings using the details below or by %s. When they have connected to the hotspot, press \'Next\'.</string>
|
||||
<string name="hotspot_manual_wifi_ssid">Network name</string>
|
||||
<string name="hotspot_qr_wifi">Your phone is providing a Wi-Fi hotspot. People who want to download Briar can connect to the hotspot by scanning this QR code. When they have connected to the hotspot, press \'Next\'.</string>
|
||||
<string name="hotspot_no_peers_connected">No devices connected</string>
|
||||
<plurals name="hotspot_peers_connected">
|
||||
<item quantity="one">%s device connected</item>
|
||||
<item quantity="other">%s devices connected</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Download link -->
|
||||
<!-- The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code' -->
|
||||
<string name="hotspot_manual_site">Your phone is providing a Wi-Fi hotspot. People who are connected to the hotspot can download Briar by typing the following link in a web browser or %s.</string>
|
||||
<string name="hotspot_manual_site_address">Address (URL)</string>
|
||||
<string name="hotspot_qr_site">Your phone is providing a Wi-Fi hotspot. People who are connected to the hotspot can download Briar by scanning this QR code.</string>
|
||||
|
||||
<!-- e.g. Download Briar 1.2.20 -->
|
||||
<string name="website_download_title">Download %s</string>
|
||||
<string name="website_download_intro">Someone nearby shared %s with you.</string>
|
||||
<string name="website_download_outro">After the download is complete, open the downloaded file and install it.</string>
|
||||
<string name="website_troubleshooting_title">Troubleshooting</string>
|
||||
<string name="website_troubleshooting_1">If you cannot download the app, try it with a different web browser app.</string>
|
||||
<string name="website_troubleshooting_2_old">To install the downloaded app, you might need to allow installation of apps from \"Unknown sources\" in system settings. Afterwards, you may need to download the app again. We recommend disabling the \"Unknown sources\" setting after installing the app.</string>
|
||||
<string name="website_troubleshooting_2_new">To install the downloaded app, you might need to allow your browser to install unknown apps. After installing the app, we recommend removing the browser\'s permission to install unknown apps.</string>
|
||||
|
||||
<string name="hotspot_help_wifi_title">Problems with connecting to Wi-Fi:</string>
|
||||
<string name="hotspot_help_wifi_1">Try disabling and re-enabling Wi-Fi on both phones and try again.</string>
|
||||
<string name="hotspot_help_wifi_2">If your phone complains that the Wi-Fi has no Internet, tell it that you want to stay connected anyway.</string>
|
||||
<string name="hotspot_help_site_title">Problems visiting the local website:</string>
|
||||
<string name="hotspot_help_site_1">Double check that you entered the address exactly as shown. A small error can make it fail.</string>
|
||||
<string name="hotspot_help_site_2">Ensure that your phone is still connected to the correct Wi-Fi (see above) when you try to access the site.</string>
|
||||
<string name="hotspot_help_site_3">If you have a firewall app, check that it isn\'t blocking access.</string>
|
||||
<string name="hotspot_help_site_4">If you can visit the site, but not download the Briar app, try it with a different web browser app.</string>
|
||||
<string name="hotspot_help_fallback_title">Nothing works?</string>
|
||||
<string name="hotspot_help_fallback_intro">You can try to save the app as an .apk file to share in some other way. Once the file has been transferred to the other device, it can be used to install Briar.
|
||||
\n\nTip: For sharing via Bluetooth, you might need to rename the file to end with .zip first.</string>
|
||||
<string name="hotspot_help_fallback_button">Save app</string>
|
||||
|
||||
<!-- error handling -->
|
||||
<string name="hotspot_error_intro">Something went wrong while trying to share the app via Wi-Fi:</string>
|
||||
<string name="hotspot_error_no_wifi_direct">Device does not support Wi-Fi Direct</string>
|
||||
<string name="hotspot_error_start_callback_failed">Hotspot failed to start: error %s</string>
|
||||
<string name="hotspot_error_start_callback_failed_unknown">Hotspot failed to start with an unknown error, reason %d</string>
|
||||
<string name="hotspot_error_start_callback_no_group_info">Hotspot failed to start: no group info</string>
|
||||
<string name="hotspot_error_web_server_start">Error starting web server</string>
|
||||
<string name="hotspot_error_web_server_serve">Error presenting website.\n\nPlease send feedback (with anonymous data) via the Briar app if the issue persists.</string>
|
||||
<string name="hotspot_flag_test">Warning: This app was installed with Android Studio and can NOT be installed on another device.</string>
|
||||
<string name="hotspot_error_framework_busy">Unable to start the hotspot.\n\nIf you have another hotspot running or are sharing your Internet connection via Wi-Fi, try stopping that and try again afterwards.</string>
|
||||
|
||||
|
||||
<!-- Transfer Data via Removable Drives -->
|
||||
|
||||
@@ -772,7 +703,7 @@
|
||||
<string name="removable_drive_title_receive">Receive data</string>
|
||||
<string name="removable_drive_send_intro">Tap the button below to create a new file containing the encrypted messages. You can choose where the file will be saved.\n\nIf you want to save the file on a removable drive, insert the drive now.</string>
|
||||
<string name="removable_drive_send_no_data">There are currently no messages waiting to be sent to this contact.</string>
|
||||
<string name="removable_drive_send_not_supported">This contact is using an old version of Briar or an old device which does not support this feature.</string>
|
||||
<string name="removable_drive_send_not_supported">This contact is using an old version of Briar which does not yet support this feature.</string>
|
||||
<string name="removable_drive_send_button">Choose file for export</string>
|
||||
<string name="removable_drive_ongoing">Please wait for ongoing task to complete</string>
|
||||
<string name="removable_drive_receive_intro">Tap the button below to choose the file that your contact sent you.\n\nIf the file is on a removable drive, insert the drive now.</string>
|
||||
@@ -787,6 +718,72 @@
|
||||
<string name="removable_drive_error_receive_text">The selected file did not contain anything that Briar could recognize.\n\nPlease check that you chose the right file.\n\nIf your contact created the file more than 28 days ago, Briar will not be able to recognize it.</string>
|
||||
|
||||
|
||||
<!-- Share app offline -->
|
||||
<string name="hotspot_title">Share Briar offline</string>
|
||||
<string name="hotspot_intro">Share this app with someone nearby without internet connection by using your phone\'s Wi-Fi.
|
||||
\n\nYour phone will start a Wi-Fi hotspot. People nearby can connect to the hotspot and download the Briar app from your phone.</string>
|
||||
<string name="hotspot_button_start_sharing">Start hotspot</string>
|
||||
<string name="hotspot_button_stop_sharing">Stop hotspot</string>
|
||||
<string name="hotspot_progress_text_start">Setting up hotspot…</string>
|
||||
<string name="hotspot_notification_channel_title">Wi-Fi hotspot</string>
|
||||
<string name="hotspot_notification_title">Sharing Briar offline</string>
|
||||
<string name="hotspot_button_connected">Next</string>
|
||||
|
||||
<string name="permission_hotspot_location_request_body">To create a Wi-Fi hotspot, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string>
|
||||
<string name="permission_hotspot_location_denied_body">You have denied access to your location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access.</string>
|
||||
<string name="wifi_settings_title">Wi-Fi setting</string>
|
||||
<string name="wifi_settings_request_enable_body">To create a Wi-Fi hotspot, Briar needs to use Wi-Fi. Please enable it.</string>
|
||||
<string name="wifi_settings_request_denied_body">You have denied to enable Wi-Fi, but Briar needs to use Wi-Fi.\n\nPlease consider enabling it.</string>
|
||||
|
||||
<string name="hotspot_tab_manual">Manual</string>
|
||||
<string name="hotspot_scanning_a_qr_code">scanning a QR code</string>
|
||||
|
||||
<!-- Wi-Fi setup -->
|
||||
<!-- The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code' -->
|
||||
<string name="hotspot_manual_wifi">Your phone is providing a Wi-Fi hotspot. People who want to download Briar can connect to the hotspot by entering the details below or %s. When they have connected to the hotspot, press \'Next\'.</string>
|
||||
<string name="hotspot_manual_wifi_ssid">Network name (SSID)</string>
|
||||
<string name="hotspot_qr_wifi">Your phone is providing a Wi-Fi hotspot. People who want to download Briar can connect to the hotspot by scanning this QR code. When they have connected to the hotspot, press \'Next\'.</string>
|
||||
<string name="hotspot_peer_connected">Successfully connected</string>
|
||||
<string name="hotspot_peer_connected_action">Show download info</string>
|
||||
|
||||
<!-- Download link -->
|
||||
<!-- The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code' -->
|
||||
<string name="hotspot_manual_site">Your phone is providing a Wi-Fi hotspot. People who are connected to the hotspot can download Briar by typing the following link in a web browser or %s.</string>
|
||||
<string name="hotspot_manual_site_address">Address (URL)</string>
|
||||
<string name="hotspot_qr_site">Your phone is providing a Wi-Fi hotspot. People who are connected to the hotspot can download Briar by scanning this QR code.</string>
|
||||
|
||||
<!-- e.g. Download Briar 1.2.20 -->
|
||||
<string name="website_download_title">Download %s</string>
|
||||
<string name="website_download_intro">Someone nearby shared %s with you.</string>
|
||||
<string name="website_download_outro">After the download is complete, open the downloaded file and install it.</string>
|
||||
<string name="website_troubleshooting_title">Troubleshooting</string>
|
||||
<string name="website_troubleshooting_1">If you cannot download the app, try it with a different web browser app.</string>
|
||||
<string name="website_troubleshooting_2_old">To install the downloaded app, you might need to allow installation of apps from \"Unknown sources\" in system settings. Afterwards, you may need to download the app again. We recommend to undo that after successful installation.</string>
|
||||
<string name="website_troubleshooting_2_new">To install the downloaded app, you might need to allow your browser to install unknown apps. We recommend to undo that after successful installation.</string>
|
||||
|
||||
<string name="hotspot_help_wifi_title">Problems with connecting to Wi-Fi:</string>
|
||||
<string name="hotspot_help_wifi_1">Try disabling and re-enabling Wi-Fi on both phones and try again.</string>
|
||||
<string name="hotspot_help_wifi_2">If your phone complains that the Wi-Fi has no internet, tell it that you want to stay connected anyway.</string>
|
||||
<string name="hotspot_help_site_title">Problems visiting the local website:</string>
|
||||
<string name="hotspot_help_site_1">Double check that you entered the address exactly as shown. A small error can make it fail.</string>
|
||||
<string name="hotspot_help_site_2">Ensure that your phone is still connected to the correct Wi-Fi (see above) when you try to access the site.</string>
|
||||
<string name="hotspot_help_site_3">Check that you don\'t have any active firewall apps that may block the access.</string>
|
||||
<string name="hotspot_help_site_4">If you can visit the site, but not download the Briar app, try it with a different web browser app.</string>
|
||||
<string name="hotspot_help_fallback_title">Nothing works?</string>
|
||||
<string name="hotspot_help_fallback_intro">You can try to save the app as an .apk file to share in some other way. Once on the other device, it can be used to install Briar.
|
||||
\n\nTip: For sharing via Bluetooth, you might need to rename the file to end with .zip first.</string>
|
||||
<string name="hotspot_help_fallback_button">Save app install file</string>
|
||||
|
||||
<!-- error handling -->
|
||||
<string name="hotspot_error_intro">Something went wrong while trying to share the app via Wi-Fi:</string>
|
||||
<string name="hotspot_error_no_wifi_direct">Device does not support Wi-Fi Direct</string>
|
||||
<string name="hotspot_error_start_callback_failed">Hotspot failed to start: error %s</string>
|
||||
<string name="hotspot_error_start_callback_failed_unknown">Hotspot failed to start with an unknown error, reason %d</string>
|
||||
<string name="hotspot_error_start_callback_no_group_info">Hotspot failed to start: no group info</string>
|
||||
<string name="hotspot_error_web_server_start">Error starting web server!</string>
|
||||
<string name="hotspot_error_web_server_serve">Error presenting website.\n\nPlease send feedback (with anonymous data) via the Briar app if the issue persists.</string>
|
||||
<string name="hotspot_flag_test">Warning: This app was installed with Android Studio and can NOT be installed on another device.</string>
|
||||
|
||||
<!-- Screenshots -->
|
||||
|
||||
<!-- This is a name to be used in screenshots. Feel free to change it to a local name. -->
|
||||
|
||||
@@ -147,7 +147,7 @@ dependencyVerification {
|
||||
'com.vanniktech:emoji:0.6.0:emoji-0.6.0.aar:a5fcde58902305c004f03c6dc2241e718400ac4162226079791d87fac83ef639',
|
||||
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
|
||||
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
|
||||
'de.hdodenhof:circleimageview:3.1.0:circleimageview-3.1.0.aar:8e9965b54072ee159074a55df216e17d5a622c94ce915ef311b1a1f32660c7fb',
|
||||
'de.hdodenhof:circleimageview:3.0.1:circleimageview-3.0.1.aar:7b0f088436ad4dcbb36d779fd09bf2192d9cc1e1a734bb6337904a7648f97617',
|
||||
'info.guardianproject.panic:panic:1.0:panic-1.0.jar:35116ab95212e67f94577faf67b88c11a6b21cbf9178b3f5b51d3dff45203ffd',
|
||||
'info.guardianproject.trustedintents:trustedintents:0.2:trustedintents-0.2.jar:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e',
|
||||
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
|
||||
@@ -187,8 +187,8 @@ dependencyVerification {
|
||||
'org.apache.maven:maven-repository-metadata:2.2.1:maven-repository-metadata-2.2.1.jar:5fe283f47b0e7f7d95a4252af3fa7a0db4d8f080cd9df308608c0472b8f168a1',
|
||||
'org.apache.maven:maven-settings:2.2.1:maven-settings-2.2.1.jar:9a9f556713a404e770c9dbdaed7eb086078014c989291960c76fdde6db4192f7',
|
||||
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.52:bcprov-jdk15on-1.52.jar:0dc4d181e4d347893c2ddbd2e6cd5d7287fc651c03648fa64b2341c7366b1773',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',
|
||||
|
||||
@@ -2,9 +2,9 @@ dependencyVerification {
|
||||
verify = [
|
||||
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
|
||||
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
|
||||
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
|
||||
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
|
||||
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
||||
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ dependencyVerification {
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
||||
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
|
||||
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
|
||||
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
|
||||
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
|
||||
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
|
||||
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
|
||||
@@ -48,8 +48,8 @@ dependencyVerification {
|
||||
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
||||
'org.jsoup:jsoup:1.13.1:jsoup-1.13.1.jar:e2b99c0d2fa39f69f27efb1c0016390713feb2f2e02d8ea7f1c36b780271598a',
|
||||
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
|
||||
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
|
||||
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
||||
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
'org.slf4j:slf4j-api:1.7.16:slf4j-api-1.7.16.jar:e56288031f5e60652c06e7bb6e9fa410a61231ab54890f7b708fc6adc4107c5b',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import com.github.ajalt.clikt.parameters.options.default
|
||||
import com.github.ajalt.clikt.parameters.options.flag
|
||||
import com.github.ajalt.clikt.parameters.options.option
|
||||
import com.github.ajalt.clikt.parameters.types.int
|
||||
import org.bouncycastle.util.encoders.Base64.toBase64String
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons
|
||||
import org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY
|
||||
import org.spongycastle.util.encoders.Base64.toBase64String
|
||||
import java.io.File
|
||||
import java.io.File.separator
|
||||
import java.io.IOException
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.briarproject.briar.headless.contact
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.javalin.http.BadRequestResponse
|
||||
import io.javalin.http.Context
|
||||
import io.javalin.http.ForbiddenResponse
|
||||
import io.javalin.http.HttpResponseException
|
||||
import io.javalin.http.NotFoundResponse
|
||||
import org.bouncycastle.util.encoders.Base64
|
||||
import org.bouncycastle.util.encoders.DecoderException
|
||||
import org.briarproject.bramble.api.connection.ConnectionRegistry
|
||||
import org.briarproject.bramble.api.contact.ContactManager
|
||||
import org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX
|
||||
@@ -31,6 +31,8 @@ import org.briarproject.briar.headless.getFromJson
|
||||
import org.briarproject.briar.headless.json.JsonDict
|
||||
import org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400
|
||||
import org.eclipse.jetty.http.HttpStatus.FORBIDDEN_403
|
||||
import org.spongycastle.util.encoders.Base64
|
||||
import org.spongycastle.util.encoders.DecoderException
|
||||
import java.security.GeneralSecurityException
|
||||
import javax.annotation.concurrent.Immutable
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -4,8 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.javalin.http.BadRequestResponse
|
||||
import io.javalin.http.Context
|
||||
import io.javalin.http.NotFoundResponse
|
||||
import org.bouncycastle.util.encoders.Base64
|
||||
import org.bouncycastle.util.encoders.DecoderException
|
||||
import org.briarproject.bramble.api.contact.Contact
|
||||
import org.briarproject.bramble.api.contact.ContactId
|
||||
import org.briarproject.bramble.api.contact.ContactManager
|
||||
@@ -38,6 +36,8 @@ import org.briarproject.briar.headless.event.output
|
||||
import org.briarproject.briar.headless.getContactIdFromPathParam
|
||||
import org.briarproject.briar.headless.getFromJson
|
||||
import org.briarproject.briar.headless.json.JsonDict
|
||||
import org.spongycastle.util.encoders.Base64
|
||||
import org.spongycastle.util.encoders.DecoderException
|
||||
import java.util.concurrent.Executor
|
||||
import javax.annotation.concurrent.Immutable
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -11,7 +11,6 @@ import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import org.bouncycastle.util.encoders.Base64
|
||||
import org.briarproject.bramble.api.contact.ContactId
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException
|
||||
import org.briarproject.bramble.api.sync.MessageId
|
||||
@@ -40,6 +39,7 @@ import org.briarproject.briar.headless.json.JsonDict
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.spongycastle.util.encoders.Base64
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class MessagingControllerImplTest : ControllerTest() {
|
||||
@@ -213,7 +213,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
|
||||
@Test
|
||||
fun markMessageRead() {
|
||||
mockkStatic("org.briarproject.briar.headless.RouterKt")
|
||||
mockkStatic("org.bouncycastle.util.encoders.Base64")
|
||||
mockkStatic("org.spongycastle.util.encoders.Base64")
|
||||
expectGetContact()
|
||||
|
||||
val messageIdString = message.id.bytes.toString()
|
||||
|
||||
@@ -30,7 +30,7 @@ buildscript {
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.5.3'
|
||||
classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.5.0'
|
||||
classpath files('libs/gradle-witness.jar')
|
||||
}
|
||||
ext.dagger_version = "2.33"
|
||||
|
||||
Reference in New Issue
Block a user