mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
95 Commits
offline-te
...
alpha-1.3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
445ef0818c | ||
|
|
8af743db71 | ||
|
|
97bd977108 | ||
|
|
aaba9f2417 | ||
|
|
6a909b6c5c | ||
|
|
4ef92f1c39 | ||
|
|
8f392b4599 | ||
|
|
f556bc7249 | ||
|
|
e48886c95a | ||
|
|
c3977e9276 | ||
|
|
b93803060e | ||
|
|
4498187721 | ||
|
|
8666fe45b1 | ||
|
|
cd12447c2e | ||
|
|
0a79cc882a | ||
|
|
7f80b5d660 | ||
|
|
92f58e9465 | ||
|
|
387f7f1545 | ||
|
|
65e0845376 | ||
|
|
97bb695373 | ||
|
|
d8230afae3 | ||
|
|
07afb955f7 | ||
|
|
a57d668fc9 | ||
|
|
765dbcc111 | ||
|
|
ccb4f88b89 | ||
|
|
eee9e1a488 | ||
|
|
f832f663c9 | ||
|
|
032f56ad67 | ||
|
|
3f2ac528c1 | ||
|
|
d174757ef0 | ||
|
|
f457a5e831 | ||
|
|
ab2fe58d2f | ||
|
|
fe1c384aeb | ||
|
|
4c327e9874 | ||
|
|
928b951c25 | ||
|
|
ecba2a51d8 | ||
|
|
9668f62c6a | ||
|
|
dc3ba3d8f0 | ||
|
|
3f6f970d36 | ||
|
|
768356d8e2 | ||
|
|
65110090de | ||
|
|
f5cab63052 | ||
|
|
399d8adb3b | ||
|
|
b40055686b | ||
|
|
2dcecb2a46 | ||
|
|
0cc118c849 | ||
|
|
b1148ebc83 | ||
|
|
802f64e309 | ||
|
|
80749fec09 | ||
|
|
1f1ea8f3ed | ||
|
|
796cbcaf4b | ||
|
|
4cf5242aa5 | ||
|
|
8921f10ffd | ||
|
|
b60c129acf | ||
|
|
852413b36a | ||
|
|
a39b367477 | ||
|
|
8be274dc4d | ||
|
|
9ac72296c7 | ||
|
|
1405f5954a | ||
|
|
f406de6b0c | ||
|
|
0df57c82cb | ||
|
|
4853bcd724 | ||
|
|
37e95d4ce6 | ||
|
|
23acd186f7 | ||
|
|
5e98bd0b53 | ||
|
|
d7238312b1 | ||
|
|
ec40da4353 | ||
|
|
204ad8913f | ||
|
|
c0f5023b63 | ||
|
|
b3c105bfa7 | ||
|
|
68acbe5c7d | ||
|
|
12245d960c | ||
|
|
f82c2517fb | ||
|
|
fa49da68a4 | ||
|
|
cffbfdf6f2 | ||
|
|
cd126279ac | ||
|
|
bedd6f9a6e | ||
|
|
10e0c8d876 | ||
|
|
dc2ad48a7f | ||
|
|
c010dd9401 | ||
|
|
270ef76057 | ||
|
|
9d47f27293 | ||
|
|
f0687a082a | ||
|
|
edebde2bf4 | ||
|
|
71ce74c633 | ||
|
|
2dd5239b9d | ||
|
|
f0145eb8e6 | ||
|
|
556ed8fe16 | ||
|
|
ed753fd354 | ||
|
|
4ecc5e4367 | ||
|
|
b4ae480d93 | ||
|
|
9a563e0cdd | ||
|
|
c5d6ee6782 | ||
|
|
f7fdf7745e | ||
|
|
a48b60a24a |
@@ -15,8 +15,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10305
|
versionCode 10306
|
||||||
versionName "1.3.5"
|
versionName "1.3.6"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.I
|
|||||||
public class AndroidRemovableDrivePluginFactory implements
|
public class AndroidRemovableDrivePluginFactory implements
|
||||||
SimplexPluginFactory {
|
SimplexPluginFactory {
|
||||||
|
|
||||||
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
private static final long MAX_LATENCY = DAYS.toMillis(28);
|
||||||
|
|
||||||
private final Application app;
|
private final Application app;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,5 @@ public interface FeatureFlags {
|
|||||||
|
|
||||||
boolean shouldEnableConnectViaBluetooth();
|
boolean shouldEnableConnectViaBluetooth();
|
||||||
|
|
||||||
boolean shouldEnableShareAppViaOfflineHotspot();
|
|
||||||
|
|
||||||
boolean shouldEnableTransferData();
|
boolean shouldEnableTransferData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ public interface RemovableDriveTask extends Runnable {
|
|||||||
TransportProperties getTransportProperties();
|
TransportProperties getTransportProperties();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an observer to the task. The observer will be notified of state
|
* Adds an observer to the task. The observer will be notified on the
|
||||||
* changes on the event thread. If the task has already finished, the
|
* event thread of the current state of the task and any subsequent state
|
||||||
* observer will be notified of its final state.
|
* changes.
|
||||||
*/
|
*/
|
||||||
void addObserver(Consumer<State> observer);
|
void addObserver(Consumer<State> observer);
|
||||||
|
|
||||||
|
|||||||
@@ -2331,7 +2331,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setInt(2, DELIVERED.getValue());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
rs.next();
|
rs.next();
|
||||||
long total = rs.getInt(1);
|
long total = rs.getLong(1);
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
return total;
|
return total;
|
||||||
|
|||||||
@@ -30,11 +30,6 @@ public class TestFeatureFlagModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnableShareAppViaOfflineHotspot() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldEnableTransferData() {
|
public boolean shouldEnableTransferData() {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10305
|
versionCode 10306
|
||||||
versionName "1.3.5"
|
versionName "1.3.6"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -121,7 +121,6 @@ dependencies {
|
|||||||
exclude group: 'com.android.support'
|
exclude group: 'com.android.support'
|
||||||
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
|
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
|
||||||
}
|
}
|
||||||
implementation 'org.nanohttpd:nanohttpd:2.3.1'
|
|
||||||
|
|
||||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android;
|
|||||||
import org.briarproject.bramble.BrambleAndroidModule;
|
import org.briarproject.bramble.BrambleAndroidModule;
|
||||||
import org.briarproject.bramble.BrambleCoreModule;
|
import org.briarproject.bramble.BrambleCoreModule;
|
||||||
import org.briarproject.bramble.account.BriarAccountModule;
|
import org.briarproject.bramble.account.BriarAccountModule;
|
||||||
|
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
||||||
import org.briarproject.bramble.system.ClockModule;
|
import org.briarproject.bramble.system.ClockModule;
|
||||||
import org.briarproject.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
import org.briarproject.briar.android.account.SignInTestCreateAccount;
|
import org.briarproject.briar.android.account.SignInTestCreateAccount;
|
||||||
@@ -21,6 +22,7 @@ import dagger.Component;
|
|||||||
AttachmentModule.class,
|
AttachmentModule.class,
|
||||||
ClockModule.class,
|
ClockModule.class,
|
||||||
MediaModule.class,
|
MediaModule.class,
|
||||||
|
RemovableDriveModule.class,
|
||||||
BriarCoreModule.class,
|
BriarCoreModule.class,
|
||||||
BrambleAndroidModule.class,
|
BrambleAndroidModule.class,
|
||||||
BriarAccountModule.class,
|
BriarAccountModule.class,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
@@ -344,7 +343,13 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.StartupFailureActivity"
|
android:name="org.briarproject.briar.android.StartupFailureActivity"
|
||||||
android:label="@string/startup_failed_activity_title" />
|
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" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.settings.SettingsActivity"
|
android:name="org.briarproject.briar.android.settings.SettingsActivity"
|
||||||
@@ -451,11 +456,6 @@
|
|||||||
android:label="@string/pending_contact_requests"
|
android:label="@string/pending_contact_requests"
|
||||||
android:theme="@style/BriarTheme" />
|
android:theme="@style/BriarTheme" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".android.hotspot.HotspotActivity"
|
|
||||||
android:label="@string/hotspot_title"
|
|
||||||
android:theme="@style/BriarTheme" />
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #F2F2F2;
|
|
||||||
font-family: Roboto,Arial,Helvetica,sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
div#top {
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
div#bottom {
|
|
||||||
padding: 16px 32px;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
a.button {
|
|
||||||
background-color: #82C91E;
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 12px 32px !important;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 2px;
|
|
||||||
color: #000000 !important;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
text-decoration: none;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
margin: 20px auto 20px auto;
|
|
||||||
}
|
|
||||||
ol {
|
|
||||||
list-style: none;
|
|
||||||
counter-reset: briar-counter;
|
|
||||||
padding-left: 40px;
|
|
||||||
}
|
|
||||||
ol li {
|
|
||||||
counter-increment: briar-counter;
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
ol li::before {
|
|
||||||
content: counter(briar-counter);
|
|
||||||
background-color: #82C91E;
|
|
||||||
color: #000000 !important;
|
|
||||||
font-weight: bold;
|
|
||||||
border-radius: 70px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
line-height: 24px;
|
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
|
||||||
left: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="top">
|
|
||||||
<svg style="width:156px;height:47px;" viewBox="0 0 778 235">
|
|
||||||
<path style="fill:#87c214"
|
|
||||||
d="m 64.900391,0 c -9.7,0 -17.701172,7.9992183 -17.701172,17.699219 v 22.5 h 43.601562 v -22.5 C 90.800781,7.9992183 82.899219,0 73.199219,0 Z m 96.999999,0 c -9.7,0 -17.70117,7.9992183 -17.70117,17.699219 V 137.19922 h 43.60156 V 17.699219 C 187.80078,7.9992183 179.89922,0 170.19922,0 Z M 47.199219,97.800781 V 217.30078 c 0,9.7 7.901172,17.69922 17.701172,17.69922 h 8.298828 c 9.7,0 17.701172,-7.99922 17.701172,-17.69922 V 97.800781 Z m 97.000001,96.999999 v 22.5 c 0,9.7 8.00117,17.69922 17.70117,17.69922 h 8.29883 c 9.7,0 17.70117,-7.99922 17.70117,-17.69922 v -22.5 z"/>
|
|
||||||
<path style="fill:#95d220"
|
|
||||||
d="M 17.699219,47.199219 C 7.9992186,47.199219 0,55.100391 0,64.900391 v 8.298828 c 0,9.7 7.8992186,17.701172 17.699219,17.701172 H 137.19922 V 47.199219 Z m 177.101561,0 v 43.701172 h 22.5 c 9.7,0 17.69922,-7.901172 17.69922,-17.701172 v -8.298828 c 0,-9.8 -7.99922,-17.701172 -17.69922,-17.701172 z M 17.699219,144.19922 C 7.9992186,144.19922 0,152.10039 0,161.90039 v 8.29883 c 0,9.7 7.8992186,17.70117 17.699219,17.70117 h 22.5 v -43.70117 z m 80.101562,0 v 43.70117 H 217.30078 c 9.7,0 17.69922,-8.00117 17.69922,-17.70117 v -8.29883 c 0,-9.8 -7.99922,-17.70117 -17.69922,-17.70117 z"/>
|
|
||||||
<path d="M 301,60.564864 V 174.43514 h 53.31362 c 25.13729,0 38.31622,-12.58548 38.31622,-32.27441 0,-12.78766 -5.88,-22.32687 -17.63776,-27.60431 v -0.20217 c 8.91968,-5.48043 12.77339,-12.38249 12.77339,-23.140374 0,-16.238294 -11.14945,-30.648991 -34.66495,-30.648991 z m 110.68683,0 V 174.43514 h 13.37598 v -45.67022 l -1.41529,-1.41926 h 26.95811 c 15.00127,0 23.51842,5.27428 28.99185,17.04704 l 14.1887,30.04244 h 15.00139 l -16.82503,-35.52128 c -3.64896,-7.91617 -9.52848,-12.99064 -14.79921,-15.22341 v -0.20216 c 12.36593,-3.24765 22.70429,-14.41228 22.70429,-29.229734 0,-22.530633 -17.43208,-33.693671 -38.31224,-33.693671 z m 111.08726,0 V 174.43514 h 13.37992 V 60.564864 Z m 78.65821,0 -50.07469,113.870276 h 14.59701 l 12.16287,-27.40213 -0.60656,-1.41926 h 62.2336 l -0.60655,1.41926 12.16286,27.40213 h 14.59701 L 615.62098,60.564864 Z m 79.463,0 V 174.43514 h 13.37994 v -45.67022 l -1.41927,-1.41926 h 26.96209 c 15.00128,0 23.51842,5.27428 28.99185,17.04704 l 14.1887,30.04244 H 778 l -16.82503,-35.52128 c -3.64895,-7.91617 -9.52851,-12.99064 -14.79921,-15.22341 v -0.20216 c 12.36591,-3.24765 22.70427,-14.41228 22.70427,-29.229734 0,-22.530633 -17.43209,-33.693671 -38.31223,-33.693671 z M 312.96068,73.147961 h 38.72057 c 14.59584,0 22.29593,5.887175 22.29593,18.065895 0,10.148944 -6.07834,18.268094 -22.29593,18.268094 h -38.72057 l 1.41927,-1.41927 V 74.571187 Z m 110.68684,0 h 37.90786 c 13.78495,0 24.32519,5.684988 24.52791,20.908395 0,12.178724 -9.52687,20.702244 -25.94718,20.702244 h -36.48859 l 1.41529,-1.41927 V 74.571187 Z m 269.00626,0 h 37.90788 c 13.98769,0 24.53187,5.684988 24.53187,20.908395 0,12.178724 -9.52688,20.702244 -25.94718,20.702244 h -36.49257 l 1.41927,-1.41927 V 74.571187 Z m -83.92693,1.423226 h 0.20615 l 3.44509,11.366019 20.06794,45.670224 1.41924,1.41926 h -50.07071 l 1.41926,-1.41926 20.06793,-45.670224 z M 312.96068,122.06505 h 41.35294 c 16.82575,0 24.53189,7.71398 24.53189,20.09568 0,12.58468 -7.09797,19.69131 -24.53189,19.69131 h -41.35294 l 1.41927,-1.42322 v -36.94055 z"/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<h2 id="download_title">Download Briar 1.2.20</h2>
|
|
||||||
|
|
||||||
<span id="download_intro">Someone nearby shared Briar with you.</span>
|
|
||||||
|
|
||||||
<a href="/app.apk" class="button">
|
|
||||||
<svg aria-hidden="true"
|
|
||||||
style="width:24px;height:24px;margin-right:6px;vertical-align:middle;"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path fill="currentColor" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"/>
|
|
||||||
</svg>
|
|
||||||
<span id="download_button">Download Briar</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<span id="download_outro">After the download is complete, open the downloaded file and install it.</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="bottom">
|
|
||||||
<h3 id="troubleshooting_title">Troubleshooting</h3>
|
|
||||||
<ol>
|
|
||||||
<li id="troubleshooting_1">If you can't download the app, try it with a different web
|
|
||||||
browser app.
|
|
||||||
</li>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -36,11 +36,6 @@ import org.briarproject.briar.BriarCoreModule;
|
|||||||
import org.briarproject.briar.android.attachment.AttachmentModule;
|
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||||
import org.briarproject.briar.android.attachment.media.MediaModule;
|
import org.briarproject.briar.android.attachment.media.MediaModule;
|
||||||
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||||
import org.briarproject.briar.android.hotspot.AbstractTabsFragment;
|
|
||||||
import org.briarproject.briar.android.hotspot.FallbackFragment;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotIntroFragment;
|
|
||||||
import org.briarproject.briar.android.hotspot.ManualHotspotFragment;
|
|
||||||
import org.briarproject.briar.android.hotspot.QrHotspotFragment;
|
|
||||||
import org.briarproject.briar.android.logging.CachingLogHandler;
|
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||||
import org.briarproject.briar.android.removabledrive.ChooserFragment;
|
import org.briarproject.briar.android.removabledrive.ChooserFragment;
|
||||||
@@ -221,16 +216,6 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
void inject(NotificationsFragment notificationsFragment);
|
void inject(NotificationsFragment notificationsFragment);
|
||||||
|
|
||||||
void inject(HotspotIntroFragment hotspotIntroFragment);
|
|
||||||
|
|
||||||
void inject(AbstractTabsFragment abstractTabsFragment);
|
|
||||||
|
|
||||||
void inject(QrHotspotFragment qrHotspotFragment);
|
|
||||||
|
|
||||||
void inject(ManualHotspotFragment manualHotspotFragment);
|
|
||||||
|
|
||||||
void inject(FallbackFragment fallbackFragment);
|
|
||||||
|
|
||||||
void inject(ChooserFragment chooserFragment);
|
void inject(ChooserFragment chooserFragment);
|
||||||
|
|
||||||
void inject(SendFragment sendFragment);
|
void inject(SendFragment sendFragment);
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import org.briarproject.bramble.util.StringUtils;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.conversation.ConversationActivity;
|
import org.briarproject.briar.android.conversation.ConversationActivity;
|
||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.hotspot.HotspotActivity;
|
|
||||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||||
@@ -64,11 +63,9 @@ import static android.app.Notification.DEFAULT_SOUND;
|
|||||||
import static android.app.Notification.DEFAULT_VIBRATE;
|
import static android.app.Notification.DEFAULT_VIBRATE;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
||||||
import static android.app.PendingIntent.getActivity;
|
|
||||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE;
|
import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE;
|
||||||
import static androidx.core.app.NotificationCompat.CATEGORY_SERVICE;
|
import static androidx.core.app.NotificationCompat.CATEGORY_SERVICE;
|
||||||
@@ -277,7 +274,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
b.setWhen(0); // Don't show the time
|
b.setWhen(0); // Don't show the time
|
||||||
b.setOngoing(true);
|
b.setOngoing(true);
|
||||||
Intent i = new Intent(appContext, SplashScreenActivity.class);
|
Intent i = new Intent(appContext, SplashScreenActivity.class);
|
||||||
b.setContentIntent(getActivity(appContext, 0, i, 0));
|
b.setContentIntent(PendingIntent.getActivity(appContext, 0, i, 0));
|
||||||
if (SDK_INT >= 21) {
|
if (SDK_INT >= 21) {
|
||||||
b.setCategory(CATEGORY_SERVICE);
|
b.setCategory(CATEGORY_SERVICE);
|
||||||
b.setVisibility(VISIBILITY_SECRET);
|
b.setVisibility(VISIBILITY_SECRET);
|
||||||
@@ -622,11 +619,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
public void showSignInNotification() {
|
public void showSignInNotification() {
|
||||||
if (blockSignInReminder) return;
|
if (blockSignInReminder) return;
|
||||||
if (SDK_INT >= 26) {
|
if (SDK_INT >= 26) {
|
||||||
NotificationChannel channel = new NotificationChannel(
|
NotificationChannel channel =
|
||||||
REMINDER_CHANNEL_ID, appContext
|
new NotificationChannel(REMINDER_CHANNEL_ID, appContext
|
||||||
.getString(R.string.reminder_notification_channel_title),
|
.getString(
|
||||||
IMPORTANCE_LOW);
|
R.string.reminder_notification_channel_title),
|
||||||
channel.setLockscreenVisibility(VISIBILITY_SECRET);
|
IMPORTANCE_LOW);
|
||||||
|
channel.setLockscreenVisibility(
|
||||||
|
NotificationCompat.VISIBILITY_SECRET);
|
||||||
notificationManager.createNotificationChannel(channel);
|
notificationManager.createNotificationChannel(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,7 +652,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
|
|
||||||
Intent i = new Intent(appContext, SplashScreenActivity.class);
|
Intent i = new Intent(appContext, SplashScreenActivity.class);
|
||||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
b.setContentIntent(getActivity(appContext, 0, i, 0));
|
b.setContentIntent(PendingIntent.getActivity(appContext, 0, i, 0));
|
||||||
|
|
||||||
notificationManager.notify(REMINDER_NOTIFICATION_ID, b.build());
|
notificationManager.notify(REMINDER_NOTIFICATION_ID, b.build());
|
||||||
}
|
}
|
||||||
@@ -721,40 +720,4 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
public void unblockAllBlogPostNotifications() {
|
public void unblockAllBlogPostNotifications() {
|
||||||
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = false);
|
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showHotspotNotification() {
|
|
||||||
if (SDK_INT >= 26) {
|
|
||||||
String channelTitle = appContext
|
|
||||||
.getString(R.string.hotspot_notification_channel_title);
|
|
||||||
NotificationChannel channel = new NotificationChannel(
|
|
||||||
HOTSPOT_CHANNEL_ID, channelTitle, IMPORTANCE_LOW);
|
|
||||||
channel.setLockscreenVisibility(VISIBILITY_SECRET);
|
|
||||||
notificationManager.createNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
BriarNotificationBuilder b =
|
|
||||||
new BriarNotificationBuilder(appContext, HOTSPOT_CHANNEL_ID);
|
|
||||||
b.setSmallIcon(R.drawable.notification_hotspot);
|
|
||||||
b.setColorRes(R.color.briar_brand_green);
|
|
||||||
b.setContentTitle(
|
|
||||||
appContext.getText(R.string.hotspot_notification_title));
|
|
||||||
b.setNotificationCategory(CATEGORY_SERVICE);
|
|
||||||
b.setOngoing(true);
|
|
||||||
b.setShowWhen(true);
|
|
||||||
|
|
||||||
String actionTitle =
|
|
||||||
appContext.getString(R.string.hotspot_button_stop_sharing);
|
|
||||||
Intent i = new Intent(appContext, HotspotActivity.class);
|
|
||||||
i.addFlags(FLAG_ACTIVITY_SINGLE_TOP);
|
|
||||||
i.setAction(ACTION_STOP_HOTSPOT);
|
|
||||||
PendingIntent actionIntent = getActivity(appContext, 0, i, 0);
|
|
||||||
int icon = SDK_INT >= 21 ? R.drawable.ic_portable_wifi_off : 0;
|
|
||||||
b.addAction(icon, actionTitle, actionIntent);
|
|
||||||
notificationManager.notify(HOTSPOT_NOTIFICATION_ID, b.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearHotspotNotification() {
|
|
||||||
notificationManager.cancel(HOTSPOT_NOTIFICATION_ID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import org.briarproject.briar.android.blog.BlogModule;
|
|||||||
import org.briarproject.briar.android.contact.ContactListModule;
|
import org.briarproject.briar.android.contact.ContactListModule;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactModule;
|
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactModule;
|
||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
import org.briarproject.briar.android.hotspot.HotspotModule;
|
|
||||||
import org.briarproject.briar.android.introduction.IntroductionModule;
|
import org.briarproject.briar.android.introduction.IntroductionModule;
|
||||||
import org.briarproject.briar.android.logging.LoggingModule;
|
import org.briarproject.briar.android.logging.LoggingModule;
|
||||||
import org.briarproject.briar.android.login.LoginModule;
|
import org.briarproject.briar.android.login.LoginModule;
|
||||||
@@ -95,7 +94,6 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
GroupListModule.class,
|
GroupListModule.class,
|
||||||
GroupConversationModule.class,
|
GroupConversationModule.class,
|
||||||
SharingModule.class,
|
SharingModule.class,
|
||||||
HotspotModule.class,
|
|
||||||
TransferDataModule.class,
|
TransferDataModule.class,
|
||||||
})
|
})
|
||||||
public class AppModule {
|
public class AppModule {
|
||||||
@@ -157,7 +155,8 @@ public class AppModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
|
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
|
||||||
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
|
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
|
||||||
AndroidRemovableDrivePluginFactory drive) {
|
AndroidRemovableDrivePluginFactory drive,
|
||||||
|
FeatureFlags featureFlags) {
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
PluginConfig pluginConfig = new PluginConfig() {
|
PluginConfig pluginConfig = new PluginConfig() {
|
||||||
|
|
||||||
@@ -168,7 +167,11 @@ public class AppModule {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||||
return SDK_INT >= 19 ? singletonList(drive) : emptyList();
|
if (SDK_INT >= 19 && featureFlags.shouldEnableTransferData()) {
|
||||||
|
return singletonList(drive);
|
||||||
|
} else {
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -315,11 +318,6 @@ public class AppModule {
|
|||||||
public boolean shouldEnableTransferData() {
|
public boolean shouldEnableTransferData() {
|
||||||
return IS_DEBUG_BUILD;
|
return IS_DEBUG_BUILD;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnableShareAppViaOfflineHotspot() {
|
|
||||||
return IS_DEBUG_BUILD;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
|
|||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -34,11 +33,7 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
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.NotificationManager.IMPORTANCE_LOW;
|
||||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
|
||||||
import static android.content.Intent.ACTION_SHUTDOWN;
|
import static android.content.Intent.ACTION_SHUTDOWN;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
@@ -55,7 +50,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
|
|||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
|
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_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_ID;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
|
||||||
@@ -66,8 +60,6 @@ public class BriarService extends Service {
|
|||||||
|
|
||||||
public static String EXTRA_START_RESULT =
|
public static String EXTRA_START_RESULT =
|
||||||
"org.briarproject.briar.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 =
|
public static String EXTRA_STARTUP_FAILED =
|
||||||
"org.briarproject.briar.STARTUP_FAILED";
|
"org.briarproject.briar.STARTUP_FAILED";
|
||||||
|
|
||||||
@@ -135,12 +127,11 @@ public class BriarService extends Service {
|
|||||||
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||||
ongoingChannel.setShowBadge(false);
|
ongoingChannel.setShowBadge(false);
|
||||||
nm.createNotificationChannel(ongoingChannel);
|
nm.createNotificationChannel(ongoingChannel);
|
||||||
NotificationChannel failureChannel = new NotificationChannel(
|
// Delete the unused channel previously used for startup
|
||||||
FAILURE_CHANNEL_ID,
|
// failure notifications
|
||||||
getString(R.string.startup_failed_notification_title),
|
// TODO: Remove this ID after a reasonable upgrade period
|
||||||
IMPORTANCE_DEFAULT);
|
// (added 2021-07-12)
|
||||||
failureChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
nm.deleteNotificationChannel(FAILURE_CHANNEL_ID);
|
||||||
nm.createNotificationChannel(failureChannel);
|
|
||||||
}
|
}
|
||||||
Notification foregroundNotification =
|
Notification foregroundNotification =
|
||||||
notificationManager.getForegroundNotification();
|
notificationManager.getForegroundNotification();
|
||||||
@@ -156,7 +147,7 @@ public class BriarService extends Service {
|
|||||||
} else {
|
} else {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING))
|
||||||
LOG.warning("Startup failed: " + result);
|
LOG.warning("Startup failed: " + result);
|
||||||
showStartupFailureNotification(result);
|
showStartupFailure(result);
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
}, "LifecycleStartup");
|
}, "LifecycleStartup");
|
||||||
@@ -182,29 +173,13 @@ public class BriarService extends Service {
|
|||||||
Localizer.getInstance().setLocale(this);
|
Localizer.getInstance().setLocale(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showStartupFailureNotification(StartResult result) {
|
private void showStartupFailure(StartResult result) {
|
||||||
androidExecutor.runOnUiThread(() -> {
|
androidExecutor.runOnUiThread(() -> {
|
||||||
NotificationCompat.Builder b = new NotificationCompat.Builder(
|
// Bring the entry activity to the front to clear the back stack
|
||||||
BriarService.this, FAILURE_CHANNEL_ID);
|
Intent i = new Intent(BriarService.this, ENTRY_ACTIVITY);
|
||||||
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.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i.putExtra(EXTRA_STARTUP_FAILED, true);
|
i.putExtra(EXTRA_STARTUP_FAILED, true);
|
||||||
|
i.putExtra(EXTRA_START_RESULT, result);
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
@@ -15,9 +14,7 @@ import org.briarproject.briar.android.fragment.ErrorFragment;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
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;
|
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -41,14 +38,6 @@ public class StartupFailureActivity extends BaseActivity implements
|
|||||||
private void handleIntent(Intent i) {
|
private void handleIntent(Intent i) {
|
||||||
StartResult result =
|
StartResult result =
|
||||||
(StartResult) i.getSerializableExtra(EXTRA_START_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
|
// show proper error message
|
||||||
int errorRes;
|
int errorRes;
|
||||||
@@ -78,5 +67,4 @@ public class StartupFailureActivity extends BaseActivity implements
|
|||||||
public void runOnDbThread(@NonNull Runnable runnable) {
|
public void runOnDbThread(@NonNull Runnable runnable) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import org.briarproject.briar.android.forum.CreateForumActivity;
|
|||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||||
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||||
import org.briarproject.briar.android.hotspot.HotspotActivity;
|
|
||||||
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||||
@@ -178,8 +177,6 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(CrashReportActivity crashReportActivity);
|
void inject(CrashReportActivity crashReportActivity);
|
||||||
|
|
||||||
void inject(HotspotActivity hotspotActivity);
|
|
||||||
|
|
||||||
void inject(RemovableDriveActivity activity);
|
void inject(RemovableDriveActivity activity);
|
||||||
|
|
||||||
// Fragments
|
// Fragments
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import static java.util.logging.Level.INFO;
|
|||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.showFragment;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Warning: Some activities don't extend {@link BaseActivity}.
|
* Warning: Some activities don't extend {@link BaseActivity}.
|
||||||
@@ -178,7 +177,13 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
|
|
||||||
public void showNextFragment(BaseFragment f) {
|
public void showNextFragment(BaseFragment f) {
|
||||||
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) return;
|
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) return;
|
||||||
showFragment(getSupportFragmentManager(), f, f.getUniqueTag());
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.step_next_in,
|
||||||
|
R.anim.step_previous_out, R.anim.step_previous_in,
|
||||||
|
R.anim.step_next_out)
|
||||||
|
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||||
|
.addToBackStack(f.getUniqueTag())
|
||||||
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isFragmentAdded(String fragmentTag) {
|
protected boolean isFragmentAdded(String fragmentTag) {
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Contact
|
|||||||
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening;
|
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted;
|
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
|
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
|
||||||
import org.briarproject.briar.android.util.QrCodeUtils;
|
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.util;
|
package org.briarproject.briar.android.contact.add.nearby;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
@@ -22,12 +22,12 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class QrCodeUtils {
|
class QrCodeUtils {
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(QrCodeUtils.class.getName());
|
private static final Logger LOG = getLogger(QrCodeUtils.class.getName());
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Bitmap createQrCode(DisplayMetrics dm, String input) {
|
static Bitmap createQrCode(DisplayMetrics dm, String input) {
|
||||||
int smallestDimen = Math.min(dm.widthPixels, dm.heightPixels);
|
int smallestDimen = Math.min(dm.widthPixels, dm.heightPixels);
|
||||||
try {
|
try {
|
||||||
// Generate QR code
|
// Generate QR code
|
||||||
@@ -17,7 +17,7 @@ import androidx.annotation.Nullable;
|
|||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ErrorFragment extends BaseFragment {
|
public class ErrorFragment extends BaseFragment {
|
||||||
|
|
||||||
public static final String TAG = ErrorFragment.class.getName();
|
private static final String TAG = ErrorFragment.class.getName();
|
||||||
|
|
||||||
private static final String ERROR_MSG = "errorMessage";
|
private static final String ERROR_MSG = "errorMessage";
|
||||||
|
|
||||||
@@ -40,7 +40,8 @@ public class ErrorFragment extends BaseFragment {
|
|||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Bundle args = requireArguments();
|
Bundle args = getArguments();
|
||||||
|
if (args == null) throw new AssertionError();
|
||||||
errorMessage = args.getString(ERROR_MSG);
|
errorMessage = args.getString(ERROR_MSG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import com.google.android.material.tabs.TabLayout;
|
|
||||||
import com.google.android.material.tabs.TabLayoutMediator;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
|
||||||
|
|
||||||
import static androidx.core.app.ActivityCompat.finishAfterTransition;
|
|
||||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.showFragment;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public abstract class AbstractTabsFragment extends Fragment {
|
|
||||||
|
|
||||||
static String ARG_FOR_WIFI_CONNECT = "forWifiConnect";
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
protected HotspotViewModel viewModel;
|
|
||||||
|
|
||||||
protected Button stopButton;
|
|
||||||
protected Button connectedButton;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
getAndroidComponent(requireContext()).inject(this);
|
|
||||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
|
||||||
.get(HotspotViewModel.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
return inflater
|
|
||||||
.inflate(R.layout.fragment_hotspot_tabs, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
|
||||||
TabAdapter tabAdapter = new TabAdapter(this);
|
|
||||||
ViewPager2 viewPager = view.findViewById(R.id.pager);
|
|
||||||
viewPager.setAdapter(tabAdapter);
|
|
||||||
TabLayout tabLayout = view.findViewById(R.id.tabLayout);
|
|
||||||
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
|
|
||||||
// tabs are set in XML, but are just dummies that don't get added
|
|
||||||
if (position == 0) {
|
|
||||||
tab.setText(R.string.hotspot_tab_manual);
|
|
||||||
tab.setIcon(R.drawable.forum_item_create_white);
|
|
||||||
} else if (position == 1) {
|
|
||||||
tab.setText(R.string.qr_code);
|
|
||||||
tab.setIcon(R.drawable.ic_qr_code);
|
|
||||||
} else throw new AssertionError();
|
|
||||||
}).attach();
|
|
||||||
|
|
||||||
stopButton = view.findViewById(R.id.stopButton);
|
|
||||||
stopButton.setOnClickListener(v -> {
|
|
||||||
// also clears hotspot
|
|
||||||
finishAfterTransition(requireActivity());
|
|
||||||
});
|
|
||||||
connectedButton = view.findViewById(R.id.connectedButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.hotspot_help_action, menu);
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.action_help) {
|
|
||||||
Fragment f = new HotspotHelpFragment();
|
|
||||||
String tag = HotspotHelpFragment.TAG;
|
|
||||||
showFragment(getParentFragmentManager(), f, tag);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Fragment getFirstFragment();
|
|
||||||
|
|
||||||
protected abstract Fragment getSecondFragment();
|
|
||||||
|
|
||||||
private class TabAdapter extends FragmentStateAdapter {
|
|
||||||
private TabAdapter(Fragment fragment) {
|
|
||||||
super(fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Fragment createFragment(int position) {
|
|
||||||
if (position == 0) return getFirstFragment();
|
|
||||||
if (position == 1) return getSecondFragment();
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
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 androidx.activity.result.ActivityResult;
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
|
|
||||||
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.
|
|
||||||
* <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.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
class ConditionManager {
|
|
||||||
|
|
||||||
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(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this to reset state when UI starts,
|
|
||||||
* because state might have changed.
|
|
||||||
*/
|
|
||||||
void resetPermissions() {
|
|
||||||
locationPermission = Permission.UNKNOWN;
|
|
||||||
wifiSetting = Permission.SHOW_RATIONALE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This makes a request for location permission.
|
|
||||||
* If {@link #checkAndRequestConditions()} returns true, you can continue.
|
|
||||||
*/
|
|
||||||
void startConditionChecks() {
|
|
||||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if conditions are fulfilled and flow can continue.
|
|
||||||
*/
|
|
||||||
boolean checkAndRequestConditions() {
|
|
||||||
if (areEssentialPermissionsGranted()) return true;
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
Intent i = SDK_INT < 29 ?
|
|
||||||
new Intent(Settings.ACTION_WIFI_SETTINGS) :
|
|
||||||
new Intent(Settings.Panel.ACTION_WIFI);
|
|
||||||
wifiRequest.launch(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
|
||||||
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 java.util.List;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
import static android.content.Intent.ACTION_SEND;
|
|
||||||
import static android.content.Intent.EXTRA_STREAM;
|
|
||||||
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
|
||||||
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 {
|
|
||||||
|
|
||||||
public static final String TAG = FallbackFragment.class.getName();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
private HotspotViewModel viewModel;
|
|
||||||
private final ActivityResultLauncher<String> launcher =
|
|
||||||
registerForActivityResult(new CreateDocument(),
|
|
||||||
this::onDocumentCreated);
|
|
||||||
private Button fallbackButton;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUniqueTag() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
FragmentActivity activity = requireActivity();
|
|
||||||
getAndroidComponent(activity).inject(this);
|
|
||||||
viewModel = new ViewModelProvider(activity, viewModelFactory)
|
|
||||||
.get(HotspotViewModel.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
return inflater
|
|
||||||
.inflate(R.layout.fragment_hotspot_save_apk, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(v, savedInstanceState);
|
|
||||||
|
|
||||||
fallbackButton = v.findViewById(R.id.fallbackButton);
|
|
||||||
progressBar = v.findViewById(R.id.progressBar);
|
|
||||||
fallbackButton.setOnClickListener(view -> {
|
|
||||||
beginDelayedTransition((ViewGroup) v);
|
|
||||||
fallbackButton.setVisibility(INVISIBLE);
|
|
||||||
progressBar.setVisibility(VISIBLE);
|
|
||||||
|
|
||||||
if (SDK_INT >= 19) launcher.launch(getApkFileName());
|
|
||||||
else viewModel.exportApk();
|
|
||||||
});
|
|
||||||
viewModel.getSavedApkToUri()
|
|
||||||
.observeEvent(this, uri -> shareUri(this, uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDocumentCreated(@Nullable Uri uri) {
|
|
||||||
showButton();
|
|
||||||
if (uri != null) viewModel.exportApk(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showButton() {
|
|
||||||
beginDelayedTransition((ViewGroup) requireView());
|
|
||||||
fallbackButton.setVisibility(VISIBLE);
|
|
||||||
progressBar.setVisibility(INVISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = fragment.requireContext();
|
|
||||||
if (SDK_INT <= 19) {
|
|
||||||
// Workaround for Android bug:
|
|
||||||
// ctx.grantUriPermission also needed for Android 4
|
|
||||||
List<ResolveInfo> resInfoList = ctx.getPackageManager()
|
|
||||||
.queryIntentActivities(i, MATCH_DEFAULT_ONLY);
|
|
||||||
for (ResolveInfo resolveInfo : resInfoList) {
|
|
||||||
String packageName = resolveInfo.activityInfo.packageName;
|
|
||||||
ctx.grantUriPermission(packageName, uri,
|
|
||||||
FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment.startActivity(Intent.createChooser(i, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.HotspotError;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.HotspotStarted;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.showFragment;
|
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ACTION_STOP_HOTSPOT;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class HotspotActivity extends BriarActivity
|
|
||||||
implements BaseFragmentListener {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
private HotspotViewModel viewModel;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectActivity(ActivityComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
|
||||||
.get(HotspotViewModel.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_fragment_container);
|
|
||||||
|
|
||||||
ActionBar ab = getSupportActionBar();
|
|
||||||
if (ab != null) {
|
|
||||||
ab.setDisplayHomeAsUpEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
|
||||||
viewModel.getState().observe(this, hotspotState -> {
|
|
||||||
if (hotspotState instanceof HotspotStarted) {
|
|
||||||
HotspotStarted started = (HotspotStarted) hotspotState;
|
|
||||||
String tag = HotspotFragment.TAG;
|
|
||||||
// check if fragment is already added
|
|
||||||
// to not lose state on configuration changes
|
|
||||||
if (fm.findFragmentByTag(tag) == null) {
|
|
||||||
if (!started.consume()) {
|
|
||||||
showFragment(fm, new HotspotFragment(), tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (hotspotState instanceof HotspotError) {
|
|
||||||
HotspotError error = ((HotspotError) hotspotState);
|
|
||||||
showErrorFragment(error.getError());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
// If there is no saved instance state, just start with the intro fragment.
|
|
||||||
fm.beginTransaction()
|
|
||||||
.replace(R.id.fragmentContainer, new HotspotIntroFragment(),
|
|
||||||
HotspotIntroFragment.TAG)
|
|
||||||
.commit();
|
|
||||||
} else if (viewModel.getState().getValue() == null) {
|
|
||||||
// If there is saved instance state, then there's either been an
|
|
||||||
// configuration change like rotated device or the activity has been
|
|
||||||
// destroyed and is now being re-created.
|
|
||||||
// In the latter case, the view model will have been destroyed, too.
|
|
||||||
// The activity can only have been destroyed if the user navigated
|
|
||||||
// away from the HotspotActivity which is nothing we
|
|
||||||
// intend to support, so we want to detect that and start from scratch
|
|
||||||
// in this case. We need to clean up existing fragments in order not
|
|
||||||
// to stack new fragments on top of old ones.
|
|
||||||
|
|
||||||
// If it is a configuration change and we moved past the intro
|
|
||||||
// fragment already, then the view model state will be != null,
|
|
||||||
// hence we can use this check for null to determine the destroyed
|
|
||||||
// activity. It can also be null if the user has not pressed
|
|
||||||
// "start sharing" yet, but in that case it won't harm to start from
|
|
||||||
// scratch.
|
|
||||||
|
|
||||||
Fragment current = fm.findFragmentById(R.id.fragmentContainer);
|
|
||||||
if (current instanceof HotspotIntroFragment) {
|
|
||||||
// If the currently displayed fragment is the intro fragment,
|
|
||||||
// there's nothing we need to do.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove everything from the back stack.
|
|
||||||
fm.popBackStackImmediate(null,
|
|
||||||
FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
|
||||||
|
|
||||||
// Start fresh with the intro fragment.
|
|
||||||
fm.beginTransaction()
|
|
||||||
.replace(R.id.fragmentContainer, new HotspotIntroFragment(),
|
|
||||||
HotspotIntroFragment.TAG)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showErrorFragment(String error) {
|
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
|
||||||
String tag = HotspotErrorFragment.TAG;
|
|
||||||
if (fm.findFragmentByTag(tag) == null) {
|
|
||||||
Fragment f = HotspotErrorFragment.newInstance(error);
|
|
||||||
showFragment(fm, f, tag, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == android.R.id.home) {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onNewIntent(Intent intent) {
|
|
||||||
super.onNewIntent(intent);
|
|
||||||
if (ACTION_STOP_HOTSPOT.equals(intent.getAction())) {
|
|
||||||
// also closes hotspot
|
|
||||||
supportFinishAfterTransition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
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 javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback;
|
|
||||||
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class HotspotErrorFragment extends BaseFragment {
|
|
||||||
|
|
||||||
public static final String TAG = HotspotErrorFragment.class.getName();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
private static final String ERROR_MSG = "errorMessage";
|
|
||||||
|
|
||||||
public static HotspotErrorFragment newInstance(String message) {
|
|
||||||
HotspotErrorFragment f = new HotspotErrorFragment();
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString(ERROR_MSG, message);
|
|
||||||
f.setArguments(args);
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String errorMessage;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUniqueTag() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
Bundle args = requireArguments();
|
|
||||||
errorMessage = args.getString(ERROR_MSG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
requireActivity().setTitle(R.string.error);
|
|
||||||
return inflater
|
|
||||||
.inflate(R.layout.fragment_hotspot_error, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(v, savedInstanceState);
|
|
||||||
TextView msg = v.findViewById(R.id.errorMessageDetail);
|
|
||||||
msg.setText(errorMessage);
|
|
||||||
|
|
||||||
Button feedbackButton = v.findViewById(R.id.feedbackButton);
|
|
||||||
feedbackButton.setOnClickListener(
|
|
||||||
button -> triggerFeedback(requireContext(), errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.showFragment;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class HotspotFragment extends AbstractTabsFragment {
|
|
||||||
|
|
||||||
public final static String TAG = HotspotFragment.class.getName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
connectedButton.setOnClickListener(v -> showNextFragment());
|
|
||||||
viewModel.getPeerConnectedEvent().observeEvent(getViewLifecycleOwner(),
|
|
||||||
this::onPeerConnected);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Fragment getFirstFragment() {
|
|
||||||
return ManualHotspotFragment.newInstance(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Fragment getSecondFragment() {
|
|
||||||
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;
|
|
||||||
showFragment(getParentFragmentManager(), f, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class HotspotHelpFragment extends Fragment {
|
|
||||||
|
|
||||||
public final static String TAG = HotspotHelpFragment.class.getName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
return inflater
|
|
||||||
.inflate(R.layout.fragment_hotspot_help, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
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;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
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 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.view.View.INVISIBLE;
|
|
||||||
import static android.view.View.VISIBLE;
|
|
||||||
import static androidx.transition.TransitionManager.beginDelayedTransition;
|
|
||||||
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG;
|
|
||||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class HotspotIntroFragment extends Fragment {
|
|
||||||
|
|
||||||
public final static String TAG = HotspotIntroFragment.class.getName();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
private HotspotViewModel viewModel;
|
|
||||||
private ConditionManager conditionManager;
|
|
||||||
|
|
||||||
private Button startButton;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
private TextView progressTextView;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
super.onAttach(context);
|
|
||||||
FragmentActivity activity = requireActivity();
|
|
||||||
getAndroidComponent(activity).inject(this);
|
|
||||||
viewModel = new ViewModelProvider(activity, viewModelFactory)
|
|
||||||
.get(HotspotViewModel.class);
|
|
||||||
conditionManager =
|
|
||||||
new ConditionManager(activity, locationRequest, wifiRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
View v = inflater
|
|
||||||
.inflate(R.layout.fragment_hotspot_intro, container, false);
|
|
||||||
|
|
||||||
startButton = v.findViewById(R.id.startButton);
|
|
||||||
progressBar = v.findViewById(R.id.progressBar);
|
|
||||||
progressTextView = v.findViewById(R.id.progressTextView);
|
|
||||||
|
|
||||||
startButton.setOnClickListener(button -> {
|
|
||||||
startButton.setEnabled(false);
|
|
||||||
conditionManager.startConditionChecks();
|
|
||||||
});
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
conditionManager.resetPermissions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startHotspot() {
|
|
||||||
startButton.setEnabled(true);
|
|
||||||
if (conditionManager.checkAndRequestConditions()) {
|
|
||||||
showInstallWarningIfNeeded();
|
|
||||||
beginDelayedTransition((ViewGroup) requireView());
|
|
||||||
startButton.setVisibility(INVISIBLE);
|
|
||||||
progressBar.setVisibility(VISIBLE);
|
|
||||||
progressTextView.setVisibility(VISIBLE);
|
|
||||||
viewModel.startHotspot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showInstallWarningIfNeeded() {
|
|
||||||
Context ctx = requireContext();
|
|
||||||
ApplicationInfo applicationInfo;
|
|
||||||
try {
|
|
||||||
applicationInfo = ctx.getPackageManager()
|
|
||||||
.getApplicationInfo(ctx.getPackageName(), 0);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
// test only apps can not be installed
|
|
||||||
if ((applicationInfo.flags & FLAG_TEST_ONLY) == FLAG_TEST_ONLY) {
|
|
||||||
int color = getResources().getColor(R.color.briar_red_500);
|
|
||||||
Snackbar.make(requireView(), R.string.hotspot_flag_test,
|
|
||||||
LENGTH_LONG).setBackgroundTint(color).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,396 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.net.wifi.WifiManager;
|
|
||||||
import android.net.wifi.p2p.WifiP2pConfig;
|
|
||||||
import android.net.wifi.p2p.WifiP2pGroup;
|
|
||||||
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.util.DisplayMetrics;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.NetworkConfig;
|
|
||||||
import org.briarproject.briar.android.util.QrCodeUtils;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
import static android.content.Context.WIFI_P2P_SERVICE;
|
|
||||||
import static android.content.Context.WIFI_SERVICE;
|
|
||||||
import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
|
|
||||||
import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
|
|
||||||
import static android.net.wifi.p2p.WifiP2pConfig.GROUP_OWNER_BAND_2GHZ;
|
|
||||||
import static android.net.wifi.p2p.WifiP2pManager.BUSY;
|
|
||||||
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 java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.handleException;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
class HotspotManager implements ActionListener {
|
|
||||||
|
|
||||||
interface HotspotListener {
|
|
||||||
void onStartingHotspot();
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void onHotspotStarted(NetworkConfig networkConfig);
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onDeviceConnected();
|
|
||||||
|
|
||||||
void onHotspotStopped();
|
|
||||||
|
|
||||||
void onHotspotError(String error);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(HotspotManager.class.getName());
|
|
||||||
|
|
||||||
private static final int MAX_GROUP_INFO_ATTEMPTS = 5;
|
|
||||||
private static final int RETRY_DELAY_MILLIS = 1000;
|
|
||||||
private static final String HOTSPOT_NAMESPACE = "hotspot";
|
|
||||||
private static final String HOTSPOT_KEY_SSID = "ssid";
|
|
||||||
private static final String HOTSPOT_KEY_PASS = "pass";
|
|
||||||
|
|
||||||
private final Context ctx;
|
|
||||||
@DatabaseExecutor
|
|
||||||
private final Executor dbExecutor;
|
|
||||||
@IoExecutor
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final AndroidExecutor androidExecutor;
|
|
||||||
private final SettingsManager settingsManager;
|
|
||||||
private final SecureRandom random;
|
|
||||||
private final WifiManager wifiManager;
|
|
||||||
private final WifiP2pManager wifiP2pManager;
|
|
||||||
private final Handler handler;
|
|
||||||
private final String lockTag;
|
|
||||||
|
|
||||||
private HotspotListener listener;
|
|
||||||
private WifiManager.WifiLock wifiLock;
|
|
||||||
private WifiP2pManager.Channel channel;
|
|
||||||
@RequiresApi(29)
|
|
||||||
private volatile NetworkConfig savedNetworkConfig;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
HotspotManager(Application ctx,
|
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
|
||||||
@IoExecutor Executor ioExecutor,
|
|
||||||
AndroidExecutor androidExecutor,
|
|
||||||
SettingsManager settingsManager,
|
|
||||||
SecureRandom random) {
|
|
||||||
this.ctx = ctx.getApplicationContext();
|
|
||||||
this.dbExecutor = dbExecutor;
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.androidExecutor = androidExecutor;
|
|
||||||
this.settingsManager = settingsManager;
|
|
||||||
this.random = random;
|
|
||||||
wifiManager = (WifiManager) ctx.getApplicationContext()
|
|
||||||
.getSystemService(WIFI_SERVICE);
|
|
||||||
wifiP2pManager =
|
|
||||||
(WifiP2pManager) ctx.getSystemService(WIFI_P2P_SERVICE);
|
|
||||||
handler = new Handler(ctx.getMainLooper());
|
|
||||||
lockTag = ctx.getPackageName() + ":app-sharing-hotspot";
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHotspotListener(HotspotListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void startWifiP2pHotspot() {
|
|
||||||
if (wifiP2pManager == null) {
|
|
||||||
listener.onHotspotError(
|
|
||||||
ctx.getString(R.string.hotspot_error_no_wifi_direct));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
listener.onStartingHotspot();
|
|
||||||
channel = wifiP2pManager.initialize(ctx, ctx.getMainLooper(), null);
|
|
||||||
if (channel == null) {
|
|
||||||
listener.onHotspotError(
|
|
||||||
ctx.getString(R.string.hotspot_error_no_wifi_direct));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (SDK_INT >= 29) {
|
|
||||||
dbExecutor.execute(() -> {
|
|
||||||
// load savedNetworkConfig before starting hotspot
|
|
||||||
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 {
|
|
||||||
acquireLock();
|
|
||||||
wifiP2pManager.createGroup(channel, this);
|
|
||||||
}
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
// this should never happen, because we request permissions before
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 {
|
|
||||||
// all cases covered, in doubt set to error
|
|
||||||
releaseHotspotWithError(ctx.getString(
|
|
||||||
R.string.hotspot_error_start_callback_failed_unknown,
|
|
||||||
reason));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopWifiP2pHotspot() {
|
|
||||||
if (channel == null) return;
|
|
||||||
wifiP2pManager.removeGroup(channel, new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess() {
|
|
||||||
releaseHotspot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(int reason) {
|
|
||||||
// not propagating back error
|
|
||||||
releaseHotspot();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
wifiLock = wifiManager.createWifiLock(lockType, lockTag);
|
|
||||||
wifiLock.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseHotspot() {
|
|
||||||
listener.onHotspotStopped();
|
|
||||||
closeChannelAndReleaseLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseHotspotWithError(String error) {
|
|
||||||
listener.onHotspotError(error);
|
|
||||||
closeChannelAndReleaseLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeChannelAndReleaseLock() {
|
|
||||||
if (SDK_INT >= 27) channel.close();
|
|
||||||
channel = null;
|
|
||||||
wifiLock.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestGroupInfo(int attempt) {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("requestGroupInfo attempt: " + attempt);
|
|
||||||
}
|
|
||||||
GroupInfoListener groupListener = group -> {
|
|
||||||
boolean valid = isGroupValid(group);
|
|
||||||
// If the group is valid, set the hotspot to started. If we don't
|
|
||||||
// have any attempts left, we try what we got
|
|
||||||
if (valid || attempt >= MAX_GROUP_INFO_ATTEMPTS) {
|
|
||||||
onHotspotStarted(group);
|
|
||||||
} else {
|
|
||||||
retryRequestingGroupInfo(attempt);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
if (channel == null) return;
|
|
||||||
wifiP2pManager.requestGroupInfo(channel, groupListener);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
// this should never happen, because we request permissions before
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void onHotspotStarted(WifiP2pGroup group) {
|
|
||||||
DisplayMetrics dm = ctx.getResources().getDisplayMetrics();
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
String content = createWifiLoginString(group.getNetworkName(),
|
|
||||||
group.getPassphrase());
|
|
||||||
Bitmap qrCode = QrCodeUtils.createQrCode(dm, content);
|
|
||||||
NetworkConfig config = new NetworkConfig(group.getNetworkName(),
|
|
||||||
group.getPassphrase(), qrCode);
|
|
||||||
listener.onHotspotStarted(config);
|
|
||||||
});
|
|
||||||
requestGroupInfoForConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGroupValid(@Nullable WifiP2pGroup group) {
|
|
||||||
if (group == null) {
|
|
||||||
LOG.info("group is null");
|
|
||||||
return false;
|
|
||||||
} else if (!group.getNetworkName().startsWith("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 = savedNetworkConfig.ssid;
|
|
||||||
if (!networkName.equals(group.getNetworkName())) {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("expected networkName: " + networkName);
|
|
||||||
LOG.info("received networkName: " + group.getNetworkName());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void retryRequestingGroupInfo(int attempt) {
|
|
||||||
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),
|
|
||||||
RETRY_DELAY_MILLIS);
|
|
||||||
} else {
|
|
||||||
releaseHotspotWithError(ctx.getString(
|
|
||||||
R.string.hotspot_error_start_callback_no_group_info));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void requestGroupInfoForConnection() {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("requestGroupInfo for connection");
|
|
||||||
}
|
|
||||||
GroupInfoListener groupListener = group -> {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
if (channel == null) return;
|
|
||||||
wifiP2pManager.requestGroupInfo(channel, groupListener);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store persistent Wi-Fi SSID and passphrase in Settings to improve UX
|
|
||||||
* so that users don't have to change them when attempting to connect.
|
|
||||||
* Works only on API 29 and above.
|
|
||||||
*/
|
|
||||||
@RequiresApi(29)
|
|
||||||
@DatabaseExecutor
|
|
||||||
private void loadSavedNetworkConfig() {
|
|
||||||
try {
|
|
||||||
Settings settings = settingsManager.getSettings(HOTSPOT_NAMESPACE);
|
|
||||||
String ssid = settings.get(HOTSPOT_KEY_SSID);
|
|
||||||
String pass = settings.get(HOTSPOT_KEY_PASS);
|
|
||||||
if (ssid == null || pass == null) {
|
|
||||||
ssid = getSsid();
|
|
||||||
pass = getPassword();
|
|
||||||
settings.put(HOTSPOT_KEY_SSID, ssid);
|
|
||||||
settings.put(HOTSPOT_KEY_PASS, pass);
|
|
||||||
settingsManager.mergeSettings(settings, HOTSPOT_NAMESPACE);
|
|
||||||
}
|
|
||||||
savedNetworkConfig = new NetworkConfig(ssid, pass, null);
|
|
||||||
} catch (DbException e) {
|
|
||||||
handleException(ctx, androidExecutor, LOG, e);
|
|
||||||
// probably never happens, but if lets use non-persistent data
|
|
||||||
String ssid = getSsid();
|
|
||||||
String pass = getPassword();
|
|
||||||
savedNetworkConfig = new NetworkConfig(ssid, pass, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(29)
|
|
||||||
private String getSsid() {
|
|
||||||
return "DIRECT-" + getRandomString(2) + "-" +
|
|
||||||
getRandomString(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(29)
|
|
||||||
private String getPassword() {
|
|
||||||
return getRandomString(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String createWifiLoginString(String ssid, String password) {
|
|
||||||
// https://en.wikipedia.org/wiki/QR_code#WiFi_network_login
|
|
||||||
// do not remove the dangling ';', it can cause problems to omit it
|
|
||||||
return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;";
|
|
||||||
}
|
|
||||||
|
|
||||||
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++) {
|
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
import dagger.Binds;
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.multibindings.IntoMap;
|
|
||||||
|
|
||||||
@Module
|
|
||||||
public interface HotspotModule {
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@ViewModelKey(HotspotViewModel.class)
|
|
||||||
ViewModel bindHotspotViewModel(HotspotViewModel hotspotViewModel);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
abstract class HotspotState {
|
|
||||||
|
|
||||||
static class StartingHotspot extends HotspotState {
|
|
||||||
}
|
|
||||||
|
|
||||||
static class NetworkConfig {
|
|
||||||
final String ssid, password;
|
|
||||||
@Nullable
|
|
||||||
final Bitmap qrCode;
|
|
||||||
|
|
||||||
NetworkConfig(String ssid, String password, @Nullable Bitmap qrCode) {
|
|
||||||
this.ssid = ssid;
|
|
||||||
this.password = password;
|
|
||||||
this.qrCode = qrCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class WebsiteConfig {
|
|
||||||
final String url;
|
|
||||||
@Nullable
|
|
||||||
final Bitmap qrCode;
|
|
||||||
|
|
||||||
WebsiteConfig(String url, @Nullable Bitmap qrCode) {
|
|
||||||
this.url = url;
|
|
||||||
this.qrCode = qrCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class HotspotStarted extends HotspotState {
|
|
||||||
private final NetworkConfig networkConfig;
|
|
||||||
private final WebsiteConfig websiteConfig;
|
|
||||||
// 'consumed' is set to true once this state triggered a UI change, i.e.
|
|
||||||
// moving to the next fragment.
|
|
||||||
private boolean consumed = false;
|
|
||||||
|
|
||||||
HotspotStarted(NetworkConfig networkConfig,
|
|
||||||
WebsiteConfig websiteConfig) {
|
|
||||||
this.networkConfig = networkConfig;
|
|
||||||
this.websiteConfig = websiteConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkConfig getNetworkConfig() {
|
|
||||||
return networkConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebsiteConfig getWebsiteConfig() {
|
|
||||||
return websiteConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
boolean consume() {
|
|
||||||
boolean old = consumed;
|
|
||||||
consumed = true;
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class HotspotError extends HotspotState {
|
|
||||||
private final String error;
|
|
||||||
|
|
||||||
HotspotError(String error) {
|
|
||||||
this.error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotManager.HotspotListener;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.HotspotError;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.HotspotStarted;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.NetworkConfig;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.StartingHotspot;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.WebsiteConfig;
|
|
||||||
import org.briarproject.briar.android.hotspot.WebServerManager.WebServerListener;
|
|
||||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
|
||||||
import static android.os.Environment.DIRECTORY_DOWNLOADS;
|
|
||||||
import static android.os.Environment.getExternalStoragePublicDirectory;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
|
||||||
import static org.briarproject.briar.BuildConfig.DEBUG;
|
|
||||||
import static org.briarproject.briar.BuildConfig.VERSION_NAME;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class HotspotViewModel extends DbViewModel
|
|
||||||
implements HotspotListener, WebServerListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(HotspotViewModel.class.getName());
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final AndroidNotificationManager notificationManager;
|
|
||||||
private final HotspotManager hotspotManager;
|
|
||||||
private final WebServerManager webServerManager;
|
|
||||||
|
|
||||||
private final MutableLiveData<HotspotState> state =
|
|
||||||
new MutableLiveData<>();
|
|
||||||
private final MutableLiveEvent<Boolean> peerConnected =
|
|
||||||
new MutableLiveEvent<>();
|
|
||||||
private final MutableLiveEvent<Uri> savedApkToUri =
|
|
||||||
new MutableLiveEvent<>();
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
// Field to temporarily store the network config received via onHotspotStarted()
|
|
||||||
// in order to post it along with a HotspotStarted status
|
|
||||||
private volatile NetworkConfig networkConfig;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
HotspotViewModel(Application app,
|
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
|
||||||
LifecycleManager lifecycleManager,
|
|
||||||
TransactionManager db,
|
|
||||||
AndroidExecutor androidExecutor,
|
|
||||||
@IoExecutor Executor ioExecutor,
|
|
||||||
HotspotManager hotspotManager,
|
|
||||||
WebServerManager webServerManager,
|
|
||||||
AndroidNotificationManager notificationManager) {
|
|
||||||
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.notificationManager = notificationManager;
|
|
||||||
this.hotspotManager = hotspotManager;
|
|
||||||
this.hotspotManager.setHotspotListener(this);
|
|
||||||
this.webServerManager = webServerManager;
|
|
||||||
this.webServerManager.setListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void startHotspot() {
|
|
||||||
HotspotState s = state.getValue();
|
|
||||||
if (s instanceof HotspotStarted) {
|
|
||||||
// This can happen if the user navigates back to intro fragment and
|
|
||||||
// taps 'start sharing' again. In this case, don't try to start the
|
|
||||||
// hotspot again. Instead, just create a new, unconsumed HotspotStarted
|
|
||||||
// event with the same config.
|
|
||||||
HotspotStarted old = (HotspotStarted) s;
|
|
||||||
state.setValue(new HotspotStarted(old.getNetworkConfig(),
|
|
||||||
old.getWebsiteConfig()));
|
|
||||||
} else {
|
|
||||||
hotspotManager.startWifiP2pHotspot();
|
|
||||||
notificationManager.showHotspotNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void stopHotspot() {
|
|
||||||
ioExecutor.execute(webServerManager::stopWebServer);
|
|
||||||
hotspotManager.stopWifiP2pHotspot();
|
|
||||||
notificationManager.clearHotspotNotification();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
super.onCleared();
|
|
||||||
stopHotspot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStartingHotspot() {
|
|
||||||
state.setValue(new StartingHotspot());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@IoExecutor
|
|
||||||
public void onHotspotStarted(NetworkConfig networkConfig) {
|
|
||||||
this.networkConfig = networkConfig;
|
|
||||||
LOG.info("starting webserver");
|
|
||||||
webServerManager.startWebServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
@Override
|
|
||||||
public void onDeviceConnected() {
|
|
||||||
peerConnected.setEvent(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onHotspotStopped() {
|
|
||||||
LOG.info("stopping webserver");
|
|
||||||
ioExecutor.execute(webServerManager::stopWebServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onHotspotError(String error) {
|
|
||||||
if (LOG.isLoggable(WARNING)) {
|
|
||||||
LOG.warning("Hotspot error: " + error);
|
|
||||||
}
|
|
||||||
state.postValue(new HotspotError(error));
|
|
||||||
ioExecutor.execute(webServerManager::stopWebServer);
|
|
||||||
notificationManager.clearHotspotNotification();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@IoExecutor
|
|
||||||
public void onWebServerStarted(WebsiteConfig websiteConfig) {
|
|
||||||
NetworkConfig nc = requireNonNull(networkConfig);
|
|
||||||
state.postValue(new HotspotStarted(nc, websiteConfig));
|
|
||||||
networkConfig = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@IoExecutor
|
|
||||||
public void onWebServerError() {
|
|
||||||
state.postValue(new HotspotError(getApplication()
|
|
||||||
.getString(R.string.hotspot_error_web_server_start)));
|
|
||||||
hotspotManager.stopWifiP2pHotspot();
|
|
||||||
}
|
|
||||||
|
|
||||||
void exportApk(Uri uri) {
|
|
||||||
if (SDK_INT < 19) throw new IllegalStateException();
|
|
||||||
try {
|
|
||||||
OutputStream out = getApplication().getContentResolver()
|
|
||||||
.openOutputStream(uri, "wt");
|
|
||||||
writeApk(out, uri);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
handleException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void exportApk() {
|
|
||||||
if (SDK_INT >= 19) throw new IllegalStateException();
|
|
||||||
File path = getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS);
|
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
path.mkdirs();
|
|
||||||
File file = new File(path, getApkFileName());
|
|
||||||
try {
|
|
||||||
OutputStream out = new FileOutputStream(file);
|
|
||||||
writeApk(out, Uri.fromFile(file));
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
handleException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getApkFileName() {
|
|
||||||
return "briar" + (DEBUG ? "-debug-" : "-") + VERSION_NAME + ".apk";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeApk(OutputStream out, Uri uriToShare) {
|
|
||||||
File apk = new File(getApplication().getPackageCodePath());
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
try {
|
|
||||||
FileInputStream in = new FileInputStream(apk);
|
|
||||||
copyAndClose(in, out);
|
|
||||||
savedApkToUri.postEvent(uriToShare);
|
|
||||||
} catch (IOException e) {
|
|
||||||
handleException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveData<HotspotState> getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveEvent<Boolean> getPeerConnectedEvent() {
|
|
||||||
return peerConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveEvent<Uri> getSavedApkToUri() {
|
|
||||||
return savedApkToUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.text.style.ClickableSpan;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.util.Consumer;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
|
||||||
|
|
||||||
import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
|
|
||||||
import static android.view.View.GONE;
|
|
||||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
|
||||||
import static org.briarproject.briar.android.hotspot.AbstractTabsFragment.ARG_FOR_WIFI_CONNECT;
|
|
||||||
import static org.briarproject.briar.android.hotspot.HotspotState.HotspotStarted;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class ManualHotspotFragment extends Fragment {
|
|
||||||
|
|
||||||
public final static String TAG = ManualHotspotFragment.class.getName();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
private HotspotViewModel viewModel;
|
|
||||||
|
|
||||||
static ManualHotspotFragment newInstance(boolean forWifiConnect) {
|
|
||||||
ManualHotspotFragment f = new ManualHotspotFragment();
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putBoolean(ARG_FOR_WIFI_CONNECT, forWifiConnect);
|
|
||||||
f.setArguments(bundle);
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
getAndroidComponent(requireContext()).inject(this);
|
|
||||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
|
||||||
.get(HotspotViewModel.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
return inflater
|
|
||||||
.inflate(R.layout.fragment_hotspot_manual, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(v, savedInstanceState);
|
|
||||||
|
|
||||||
TextView manualIntroView = v.findViewById(R.id.manualIntroView);
|
|
||||||
TextView ssidLabelView = v.findViewById(R.id.ssidLabelView);
|
|
||||||
TextView ssidView = v.findViewById(R.id.ssidView);
|
|
||||||
TextView passwordView = v.findViewById(R.id.passwordView);
|
|
||||||
|
|
||||||
Consumer<HotspotStarted> consumer;
|
|
||||||
if (requireArguments().getBoolean(ARG_FOR_WIFI_CONNECT)) {
|
|
||||||
linkify(manualIntroView, R.string.hotspot_manual_wifi);
|
|
||||||
ssidLabelView.setText(R.string.hotspot_manual_wifi_ssid);
|
|
||||||
consumer = state -> {
|
|
||||||
ssidView.setText(state.getNetworkConfig().ssid);
|
|
||||||
passwordView.setText(state.getNetworkConfig().password);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
linkify(manualIntroView, R.string.hotspot_manual_site);
|
|
||||||
ssidLabelView.setText(R.string.hotspot_manual_site_address);
|
|
||||||
consumer = state -> ssidView.setText(state.getWebsiteConfig().url);
|
|
||||||
v.findViewById(R.id.passwordLabelView).setVisibility(GONE);
|
|
||||||
passwordView.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
|
|
||||||
// we only expect to be in this state here
|
|
||||||
if (state instanceof HotspotStarted) {
|
|
||||||
consumer.accept((HotspotStarted) state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void linkify(TextView textView, int resPattern) {
|
|
||||||
String pattern = getString(resPattern);
|
|
||||||
String replacement = getString(R.string.hotspot_scanning_a_qr_code);
|
|
||||||
String text = String.format(pattern, replacement);
|
|
||||||
int start = pattern.indexOf("%s");
|
|
||||||
int end = start + replacement.length();
|
|
||||||
SpannableString spannable = new SpannableString(text);
|
|
||||||
ClickableSpan clickable = new ClickableSpan() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View textView) {
|
|
||||||
ViewPager2 pager = requireActivity().findViewById(R.id.pager);
|
|
||||||
pager.setCurrentItem(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
spannable.setSpan(clickable, start, end, SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
|
|
||||||
textView.setText(spannable);
|
|
||||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
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 org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
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;
|
|
||||||
|
|
||||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
|
||||||
import static org.briarproject.briar.android.hotspot.AbstractTabsFragment.ARG_FOR_WIFI_CONNECT;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class QrHotspotFragment extends Fragment {
|
|
||||||
|
|
||||||
public final static String TAG = QrHotspotFragment.class.getName();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
|
||||||
|
|
||||||
private HotspotViewModel viewModel;
|
|
||||||
|
|
||||||
static QrHotspotFragment newInstance(boolean forWifiConnect) {
|
|
||||||
QrHotspotFragment f = new QrHotspotFragment();
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putBoolean(ARG_FOR_WIFI_CONNECT, forWifiConnect);
|
|
||||||
f.setArguments(bundle);
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
getAndroidComponent(requireContext()).inject(this);
|
|
||||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
|
||||||
.get(HotspotViewModel.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
View v = inflater
|
|
||||||
.inflate(R.layout.fragment_hotspot_qr, container, false);
|
|
||||||
|
|
||||||
TextView qrIntroView = v.findViewById(R.id.qrIntroView);
|
|
||||||
ImageView qrCodeView = v.findViewById(R.id.qrCodeView);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
consumer.accept((HotspotStarted) state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.jsoup.Jsoup;
|
|
||||||
import org.jsoup.nodes.Document;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import fi.iki.elonen.NanoHTTPD;
|
|
||||||
|
|
||||||
import static android.util.Xml.Encoding.UTF_8;
|
|
||||||
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
|
|
||||||
import static fi.iki.elonen.NanoHTTPD.Response.Status.NOT_FOUND;
|
|
||||||
import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
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.briar.BuildConfig.VERSION_NAME;
|
|
||||||
import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class WebServer extends NanoHTTPD {
|
|
||||||
|
|
||||||
final static int PORT = 9999;
|
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(WebServer.class.getName());
|
|
||||||
private static final String FILE_HTML = "hotspot.html";
|
|
||||||
private static final Pattern REGEX_AGENT =
|
|
||||||
Pattern.compile("Android ([0-9]+)");
|
|
||||||
|
|
||||||
private final Context ctx;
|
|
||||||
|
|
||||||
WebServer(Context ctx) {
|
|
||||||
super(PORT);
|
|
||||||
this.ctx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() throws IOException {
|
|
||||||
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response serve(IHTTPSession session) {
|
|
||||||
if (session.getUri().endsWith("favicon.ico")) {
|
|
||||||
return newFixedLengthResponse(NOT_FOUND, MIME_PLAINTEXT,
|
|
||||||
NOT_FOUND.getDescription());
|
|
||||||
}
|
|
||||||
if (session.getUri().endsWith(".apk")) {
|
|
||||||
return serveApk();
|
|
||||||
}
|
|
||||||
Response res;
|
|
||||||
try {
|
|
||||||
String html = getHtml(session.getHeaders().get("user-agent"));
|
|
||||||
res = newFixedLengthResponse(OK, MIME_HTML, html);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
res = newFixedLengthResponse(INTERNAL_ERROR, MIME_PLAINTEXT,
|
|
||||||
ctx.getString(R.string.hotspot_error_web_server_serve));
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getHtml(@Nullable String userAgent) throws Exception {
|
|
||||||
Document doc;
|
|
||||||
try (InputStream is = ctx.getAssets().open(FILE_HTML)) {
|
|
||||||
doc = Jsoup.parse(is, UTF_8.name(), "");
|
|
||||||
}
|
|
||||||
String app = ctx.getString(R.string.app_name);
|
|
||||||
String appV = app + " " + VERSION_NAME;
|
|
||||||
String filename = getApkFileName();
|
|
||||||
doc.select("#download_title").first()
|
|
||||||
.text(ctx.getString(R.string.website_download_title, appV));
|
|
||||||
doc.select("#download_intro").first()
|
|
||||||
.text(ctx.getString(R.string.website_download_intro, app));
|
|
||||||
doc.select(".button").first().attr("href", filename);
|
|
||||||
doc.select("#download_button").first()
|
|
||||||
.text(ctx.getString(R.string.website_download_title, app));
|
|
||||||
doc.select("#download_outro").first()
|
|
||||||
.text(ctx.getString(R.string.website_download_outro));
|
|
||||||
doc.select("#troubleshooting_title").first()
|
|
||||||
.text(ctx.getString(R.string.website_troubleshooting_title));
|
|
||||||
doc.select("#troubleshooting_1").first()
|
|
||||||
.text(ctx.getString(R.string.website_troubleshooting_1));
|
|
||||||
doc.select("#troubleshooting_2").first()
|
|
||||||
.text(getUnknownSourcesString(userAgent));
|
|
||||||
return doc.outerHtml();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getUnknownSourcesString(@Nullable String userAgent) {
|
|
||||||
boolean is8OrHigher = false;
|
|
||||||
if (userAgent != null) {
|
|
||||||
Matcher matcher = REGEX_AGENT.matcher(userAgent);
|
|
||||||
if (matcher.find()) {
|
|
||||||
int androidMajorVersion =
|
|
||||||
Integer.parseInt(requireNonNull(matcher.group(1)));
|
|
||||||
is8OrHigher = androidMajorVersion >= 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return is8OrHigher ?
|
|
||||||
ctx.getString(R.string.website_troubleshooting_2_new) :
|
|
||||||
ctx.getString(R.string.website_troubleshooting_2_old);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Response serveApk() {
|
|
||||||
String mime = "application/vnd.android.package-archive";
|
|
||||||
|
|
||||||
File file = new File(ctx.getPackageCodePath());
|
|
||||||
long fileLen = file.length();
|
|
||||||
|
|
||||||
Response res;
|
|
||||||
try {
|
|
||||||
FileInputStream fis = new FileInputStream(file);
|
|
||||||
res = newFixedLengthResponse(OK, mime, fis, fileLen);
|
|
||||||
res.addHeader("Content-Length", "" + fileLen);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
res = newFixedLengthResponse(NOT_FOUND, MIME_PLAINTEXT,
|
|
||||||
ctx.getString(R.string.hotspot_error_web_server_serve));
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.WebsiteConfig;
|
|
||||||
import org.briarproject.briar.android.util.QrCodeUtils;
|
|
||||||
|
|
||||||
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 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.briar.android.hotspot.WebServer.PORT;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
class WebServerManager {
|
|
||||||
|
|
||||||
interface WebServerListener {
|
|
||||||
@IoExecutor
|
|
||||||
void onWebServerStarted(WebsiteConfig websiteConfig);
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void onWebServerError();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(WebServerManager.class.getName());
|
|
||||||
|
|
||||||
private final WebServer webServer;
|
|
||||||
private final DisplayMetrics dm;
|
|
||||||
|
|
||||||
private WebServerListener listener;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
WebServerManager(Application ctx) {
|
|
||||||
webServer = new WebServer(ctx);
|
|
||||||
dm = ctx.getResources().getDisplayMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setListener(WebServerListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void startWebServer() {
|
|
||||||
try {
|
|
||||||
webServer.start();
|
|
||||||
onWebServerStarted();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
listener.onWebServerError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void onWebServerStarted() {
|
|
||||||
String url = "http://192.168.49.1:" + PORT;
|
|
||||||
InetAddress address = getAccessPointAddress();
|
|
||||||
if (address == null) {
|
|
||||||
LOG.info(
|
|
||||||
"Could not find access point address, assuming 192.168.49.1");
|
|
||||||
} else {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Access point address " + address.getHostAddress());
|
|
||||||
}
|
|
||||||
url = "http://" + address.getHostAddress() + ":" + PORT;
|
|
||||||
}
|
|
||||||
Bitmap qrCode = QrCodeUtils.createQrCode(dm, url);
|
|
||||||
listener.onWebServerStarted(new WebsiteConfig(url, qrCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* It is safe to call this more than once and it won't throw.
|
|
||||||
*/
|
|
||||||
@IoExecutor
|
|
||||||
void stopWebServer() {
|
|
||||||
webServer.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static InetAddress getAccessPointAddress() {
|
|
||||||
for (NetworkInterface i : getNetworkInterfaces()) {
|
|
||||||
if (i.getName().startsWith("p2p")) {
|
|
||||||
for (InterfaceAddress a : i.getInterfaceAddresses()) {
|
|
||||||
// we consider only IPv4 addresses
|
|
||||||
if (a.getAddress().getAddress().length == 4)
|
|
||||||
return a.getAddress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.briarproject.briar.android.hotspot;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
public class WebsiteFragment extends AbstractTabsFragment {
|
|
||||||
|
|
||||||
public final static String TAG = WebsiteFragment.class.getName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
connectedButton.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Fragment getFirstFragment() {
|
|
||||||
return ManualHotspotFragment.newInstance(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Fragment getSecondFragment() {
|
|
||||||
return QrHotspotFragment.newInstance(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -29,6 +29,7 @@ import org.briarproject.bramble.api.plugin.TorConstants;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
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.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.blog.FeedFragment;
|
import org.briarproject.briar.android.blog.FeedFragment;
|
||||||
@@ -73,6 +74,7 @@ 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.ENABLING;
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
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_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.TestingConstants.IS_DEBUG_BUILD;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||||
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
|
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
|
||||||
@@ -250,6 +252,11 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
|
|
||||||
private void exitIfStartupFailed(Intent intent) {
|
private void exitIfStartupFailed(Intent intent) {
|
||||||
if (intent.getBooleanExtra(EXTRA_STARTUP_FAILED, false)) {
|
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();
|
finish();
|
||||||
LOG.info("Exiting");
|
LOG.info("Exiting");
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ public class ReceiveFragment extends Fragment {
|
|||||||
private Button button;
|
private Button button;
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
|
private boolean checkForStateLoss = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
@@ -73,6 +75,10 @@ public class ReceiveFragment extends Fragment {
|
|||||||
.observeEvent(getViewLifecycleOwner(), this::onOldTaskResumed);
|
.observeEvent(getViewLifecycleOwner(), this::onOldTaskResumed);
|
||||||
viewModel.getState()
|
viewModel.getState()
|
||||||
.observe(getViewLifecycleOwner(), this::onStateChanged);
|
.observe(getViewLifecycleOwner(), this::onStateChanged);
|
||||||
|
|
||||||
|
// need to check for lost ViewModel state when creating with prior state
|
||||||
|
if (savedInstanceState != null) checkForStateLoss = true;
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +90,23 @@ public class ReceiveFragment extends Fragment {
|
|||||||
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
|
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) {
|
private void onOldTaskResumed(boolean resumed) {
|
||||||
if (resumed) {
|
if (resumed) {
|
||||||
Toast.makeText(requireContext(),
|
Toast.makeText(requireContext(),
|
||||||
@@ -104,6 +127,8 @@ public class ReceiveFragment extends Fragment {
|
|||||||
|
|
||||||
private void onDocumentChosen(@Nullable Uri uri) {
|
private void onDocumentChosen(@Nullable Uri uri) {
|
||||||
if (uri == null) return;
|
if (uri == null) return;
|
||||||
|
// we just got our document, so don't treat this as a state loss
|
||||||
|
checkForStateLoss = false;
|
||||||
viewModel.importData(uri);
|
viewModel.importData(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ class RemovableDriveViewModel extends DbViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
boolean hasNoState() {
|
||||||
|
return action.getLastValue() == null && state.getValue() == null &&
|
||||||
|
task == null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set this as soon as it becomes available.
|
* Set this as soon as it becomes available.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ public class SendFragment extends Fragment {
|
|||||||
private Button button;
|
private Button button;
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
|
private boolean checkForStateLoss = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
@@ -80,6 +82,9 @@ public class SendFragment extends Fragment {
|
|||||||
viewModel.getState()
|
viewModel.getState()
|
||||||
.observe(getViewLifecycleOwner(), this::onStateChanged);
|
.observe(getViewLifecycleOwner(), this::onStateChanged);
|
||||||
|
|
||||||
|
// need to check for lost ViewModel state when creating with prior state
|
||||||
|
if (savedInstanceState != null) checkForStateLoss = true;
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +96,23 @@ public class SendFragment extends Fragment {
|
|||||||
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
|
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) {
|
private void onOldTaskResumed(boolean resumed) {
|
||||||
if (resumed) {
|
if (resumed) {
|
||||||
Toast.makeText(requireContext(),
|
Toast.makeText(requireContext(),
|
||||||
@@ -127,6 +149,8 @@ public class SendFragment extends Fragment {
|
|||||||
|
|
||||||
private void onDocumentCreated(@Nullable Uri uri) {
|
private void onDocumentCreated(@Nullable Uri uri) {
|
||||||
if (uri == null) return;
|
if (uri == null) return;
|
||||||
|
// we just got our document, so don't treat this as a state loss
|
||||||
|
checkForStateLoss = false;
|
||||||
viewModel.exportData(uri);
|
viewModel.exportData(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class BriarExceptionHandler implements UncaughtExceptionHandler {
|
|||||||
|
|
||||||
// activity runs in its own process, so we can kill the old one
|
// activity runs in its own process, so we can kill the old one
|
||||||
startDevReportActivity(app.getApplicationContext(),
|
startDevReportActivity(app.getApplicationContext(),
|
||||||
CrashReportActivity.class, e, appStartTime, logKey, null);
|
CrashReportActivity.class, e, appStartTime, logKey);
|
||||||
Process.killProcess(Process.myPid());
|
Process.killProcess(Process.myPid());
|
||||||
System.exit(10);
|
System.exit(10);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import static java.util.Objects.requireNonNull;
|
|||||||
public class CrashReportActivity extends BaseActivity
|
public class CrashReportActivity extends BaseActivity
|
||||||
implements BaseFragmentListener {
|
implements BaseFragmentListener {
|
||||||
|
|
||||||
public static final String EXTRA_INITIAL_COMMENT = "initialComment";
|
|
||||||
public static final String EXTRA_THROWABLE = "throwable";
|
public static final String EXTRA_THROWABLE = "throwable";
|
||||||
public static final String EXTRA_APP_START_TIME = "appStartTime";
|
public static final String EXTRA_APP_START_TIME = "appStartTime";
|
||||||
public static final String EXTRA_APP_LOGCAT = "logcat";
|
public static final String EXTRA_APP_LOGCAT = "logcat";
|
||||||
@@ -56,11 +55,10 @@ public class CrashReportActivity extends BaseActivity
|
|||||||
setContentView(R.layout.activity_dev_report);
|
setContentView(R.layout.activity_dev_report);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String initialComment = intent.getStringExtra(EXTRA_INITIAL_COMMENT);
|
|
||||||
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
|
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
|
||||||
long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1);
|
long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1);
|
||||||
byte[] logKey = intent.getByteArrayExtra(EXTRA_APP_LOGCAT);
|
byte[] logKey = intent.getByteArrayExtra(EXTRA_APP_LOGCAT);
|
||||||
viewModel.init(t, appStartTime, logKey, initialComment);
|
viewModel.init(t, appStartTime, logKey);
|
||||||
viewModel.getShowReport().observeEvent(this, show -> {
|
viewModel.getShowReport().observeEvent(this, show -> {
|
||||||
if (show) displayFragment(true);
|
if (show) displayFragment(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,9 +78,6 @@ public class ReportFormFragment extends BaseFragment {
|
|||||||
list = v.findViewById(R.id.list);
|
list = v.findViewById(R.id.list);
|
||||||
progress = v.findViewById(R.id.progress_wheel);
|
progress = v.findViewById(R.id.progress_wheel);
|
||||||
|
|
||||||
if (viewModel.getInitialComment() != null)
|
|
||||||
userCommentView.setText(viewModel.getInitialComment());
|
|
||||||
|
|
||||||
if (viewModel.isFeedback()) {
|
if (viewModel.isFeedback()) {
|
||||||
includeDebugReport
|
includeDebugReport
|
||||||
.setText(getString(R.string.include_debug_report_feedback));
|
.setText(getString(R.string.include_debug_report_feedback));
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
private final MutableLiveEvent<Integer> closeReport =
|
private final MutableLiveEvent<Integer> closeReport =
|
||||||
new MutableLiveEvent<>();
|
new MutableLiveEvent<>();
|
||||||
private boolean isFeedback;
|
private boolean isFeedback;
|
||||||
@Nullable
|
|
||||||
private String initialComment;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ReportViewModel(@NonNull Application application,
|
ReportViewModel(@NonNull Application application,
|
||||||
@@ -82,8 +80,7 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void init(@Nullable Throwable t, long appStartTime,
|
void init(@Nullable Throwable t, long appStartTime,
|
||||||
@Nullable byte[] logKey, @Nullable String initialComment) {
|
@Nullable byte[] logKey) {
|
||||||
this.initialComment = initialComment;
|
|
||||||
isFeedback = t == null;
|
isFeedback = t == null;
|
||||||
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
|
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
|
||||||
String decryptedLogs;
|
String decryptedLogs;
|
||||||
@@ -106,11 +103,6 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
String getInitialComment() {
|
|
||||||
return initialComment;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isFeedback() {
|
boolean isFeedback() {
|
||||||
return isFeedback;
|
return isFeedback;
|
||||||
}
|
}
|
||||||
@@ -148,7 +140,7 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The content of the report that will be loaded after
|
* The content of the report that will be loaded after
|
||||||
* {@link #init(Throwable, long, byte[], String)} was called.
|
* {@link #init(Throwable, long, byte[])} was called.
|
||||||
*/
|
*/
|
||||||
LiveData<ReportData> getReportData() {
|
LiveData<ReportData> getReportData() {
|
||||||
return reportData;
|
return reportData;
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback";
|
private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback";
|
||||||
private static final String PREF_KEY_DEV = "pref_key_dev";
|
private static final String PREF_KEY_DEV = "pref_key_dev";
|
||||||
private static final String PREF_KEY_EXPLODE = "pref_key_explode";
|
private static final String PREF_KEY_EXPLODE = "pref_key_explode";
|
||||||
private static final String PREF_KEY_SHARE_APP = "pref_key_share_app";
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@@ -86,12 +85,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV));
|
PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV));
|
||||||
dev.setVisible(false);
|
dev.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!viewModel.shouldEnableShareAppViaOfflineHotspot()) {
|
|
||||||
Preference shareApp =
|
|
||||||
requireNonNull(findPreference(PREF_KEY_SHARE_APP));
|
|
||||||
shareApp.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -262,8 +262,4 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
|||||||
return screenLockTimeout;
|
return screenLockTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldEnableShareAppViaOfflineHotspot() {
|
|
||||||
return featureFlags.shouldEnableShareAppViaOfflineHotspot();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ import androidx.core.util.Consumer;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
@@ -113,7 +111,6 @@ import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
|
|||||||
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_INITIAL_COMMENT;
|
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE;
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -140,18 +137,13 @@ public class UiUtils {
|
|||||||
|
|
||||||
public static void showFragment(FragmentManager fm, Fragment f,
|
public static void showFragment(FragmentManager fm, Fragment f,
|
||||||
@Nullable String tag) {
|
@Nullable String tag) {
|
||||||
showFragment(fm, f, tag, true);
|
fm.beginTransaction()
|
||||||
}
|
|
||||||
|
|
||||||
public static void showFragment(FragmentManager fm, Fragment f,
|
|
||||||
@Nullable String tag, boolean addToBackStack) {
|
|
||||||
FragmentTransaction ta = fm.beginTransaction()
|
|
||||||
.setCustomAnimations(R.anim.step_next_in,
|
.setCustomAnimations(R.anim.step_next_in,
|
||||||
R.anim.step_previous_out, R.anim.step_previous_in,
|
R.anim.step_previous_out, R.anim.step_previous_in,
|
||||||
R.anim.step_next_out)
|
R.anim.step_next_out)
|
||||||
.replace(R.id.fragmentContainer, f, tag);
|
.replace(R.id.fragmentContainer, f, tag)
|
||||||
if (addToBackStack) ta.addToBackStack(tag);
|
.addToBackStack(tag)
|
||||||
ta.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getContactDisplayName(Author author,
|
public static String getContactDisplayName(Author author,
|
||||||
@@ -341,11 +333,6 @@ public class UiUtils {
|
|||||||
return i;
|
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,
|
* @return true if location is enabled,
|
||||||
* or it isn't required due to this being a SDK < 28 device.
|
* or it isn't required due to this being a SDK < 28 device.
|
||||||
@@ -423,25 +410,17 @@ public class UiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void triggerFeedback(Context ctx) {
|
public static void triggerFeedback(Context ctx) {
|
||||||
triggerFeedback(ctx, null);
|
startDevReportActivity(ctx, FeedbackActivity.class, null, null, null);
|
||||||
}
|
|
||||||
|
|
||||||
public static void triggerFeedback(Context ctx,
|
|
||||||
@Nullable String initialComment) {
|
|
||||||
startDevReportActivity(ctx, FeedbackActivity.class, null, null, null,
|
|
||||||
initialComment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startDevReportActivity(Context ctx,
|
public static void startDevReportActivity(Context ctx,
|
||||||
Class<? extends FragmentActivity> activity, @Nullable Throwable t,
|
Class<? extends FragmentActivity> activity, @Nullable Throwable t,
|
||||||
@Nullable Long appStartTime, @Nullable byte[] logKey, @Nullable
|
@Nullable Long appStartTime, @Nullable byte[] logKey) {
|
||||||
String initialComment) {
|
|
||||||
final Intent dialogIntent = new Intent(ctx, activity);
|
final Intent dialogIntent = new Intent(ctx, activity);
|
||||||
dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||||
dialogIntent.putExtra(EXTRA_THROWABLE, t);
|
dialogIntent.putExtra(EXTRA_THROWABLE, t);
|
||||||
dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime);
|
dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime);
|
||||||
dialogIntent.putExtra(EXTRA_APP_LOGCAT, logKey);
|
dialogIntent.putExtra(EXTRA_APP_LOGCAT, logKey);
|
||||||
dialogIntent.putExtra(EXTRA_INITIAL_COMMENT, initialComment);
|
|
||||||
ctx.startActivity(dialogIntent);
|
ctx.startActivity(dialogIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ public interface AndroidNotificationManager {
|
|||||||
int FORUM_POST_NOTIFICATION_ID = 6;
|
int FORUM_POST_NOTIFICATION_ID = 6;
|
||||||
int BLOG_POST_NOTIFICATION_ID = 7;
|
int BLOG_POST_NOTIFICATION_ID = 7;
|
||||||
int CONTACT_ADDED_NOTIFICATION_ID = 8;
|
int CONTACT_ADDED_NOTIFICATION_ID = 8;
|
||||||
int HOTSPOT_NOTIFICATION_ID = 9;
|
|
||||||
|
|
||||||
// Channel IDs
|
// Channel IDs
|
||||||
String CONTACT_CHANNEL_ID = "contacts";
|
String CONTACT_CHANNEL_ID = "contacts";
|
||||||
@@ -42,13 +41,13 @@ public interface AndroidNotificationManager {
|
|||||||
// that will sort below the main channels such as contacts
|
// that will sort below the main channels such as contacts
|
||||||
String ONGOING_CHANNEL_OLD_ID = "zForegroundService";
|
String ONGOING_CHANNEL_OLD_ID = "zForegroundService";
|
||||||
String ONGOING_CHANNEL_ID = "zForegroundService2";
|
String ONGOING_CHANNEL_ID = "zForegroundService2";
|
||||||
String FAILURE_CHANNEL_ID = "zStartupFailure";
|
|
||||||
String REMINDER_CHANNEL_ID = "zSignInReminder";
|
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
|
// Actions for pending intents
|
||||||
String ACTION_DISMISS_REMINDER = "dismissReminder";
|
String ACTION_DISMISS_REMINDER = "dismissReminder";
|
||||||
String ACTION_STOP_HOTSPOT = "stopHotspot";
|
|
||||||
|
|
||||||
Notification getForegroundNotification();
|
Notification getForegroundNotification();
|
||||||
|
|
||||||
@@ -97,8 +96,4 @@ public interface AndroidNotificationManager {
|
|||||||
void blockAllBlogPostNotifications();
|
void blockAllBlogPostNotifications();
|
||||||
|
|
||||||
void unblockAllBlogPostNotifications();
|
void unblockAllBlogPostNotifications();
|
||||||
|
|
||||||
void showHotspotNotification();
|
|
||||||
|
|
||||||
void clearHotspotNotification();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="#FFFFFF"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<group
|
|
||||||
android:scaleX="0.92"
|
|
||||||
android:scaleY="0.92"
|
|
||||||
android:translateX="0.96"
|
|
||||||
android:translateY="0.96">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.48 -0.81,2.75 -2,3.45l1,1.74c1.79,-1.04 3,-2.97 3,-5.19zM12,3C6.48,3 2,7.48 2,13c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,18.53 4,15.96 4,13c0,-4.42 3.58,-8 8,-8s8,3.58 8,8c0,2.96 -1.61,5.53 -4,6.92l1,1.73c2.99,-1.73 5,-4.95 5,-8.65 0,-5.52 -4.48,-10 -10,-10z" />
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 493 B |
Binary file not shown.
|
Before Width: | Height: | Size: 316 B |
Binary file not shown.
|
Before Width: | Height: | Size: 621 B |
Binary file not shown.
|
Before Width: | Height: | Size: 975 B |
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="8dp"
|
|
||||||
android:height="8dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M17.56,14.24c0.28,-0.69 0.44,-1.45 0.44,-2.24 0,-3.31 -2.69,-6 -6,-6 -0.79,0 -1.55,0.16 -2.24,0.44l1.62,1.62c0.2,-0.03 0.41,-0.06 0.62,-0.06 2.21,0 4,1.79 4,4 0,0.21 -0.02,0.42 -0.05,0.63l1.61,1.61zM12,4c4.42,0 8,3.58 8,8 0,1.35 -0.35,2.62 -0.95,3.74l1.47,1.47C21.46,15.69 22,13.91 22,12c0,-5.52 -4.48,-10 -10,-10 -1.91,0 -3.69,0.55 -5.21,1.47l1.46,1.46C9.37,4.34 10.65,4 12,4zM3.27,2.5L2,3.77l2.1,2.1C2.79,7.57 2,9.69 2,12c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,17.53 4,14.96 4,12c0,-1.76 0.57,-3.38 1.53,-4.69l1.43,1.44C6.36,9.68 6,10.8 6,12c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-0.65 0.17,-1.25 0.44,-1.79l1.58,1.58L10,12c0,1.1 0.9,2 2,2l0.21,-0.02 0.01,0.01 7.51,7.51L21,20.23 4.27,3.5l-1,-1z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M3,11h8V3H3V11zM5,5h4v4H5V5z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M3,21h8v-8H3V21zM5,15h4v4H5V15z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M13,3v8h8V3H13zM19,9h-4V5h4V9z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M19,19h2v2h-2z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M13,13h2v2h-2z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M15,15h2v2h-2z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M13,17h2v2h-2z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M15,19h2v2h-2z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M17,17h2v2h-2z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M17,13h2v2h-2z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M19,15h2v2h-2z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?android:attr/textColorPrimary"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
tools:ignore="NewApi">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFFFF"
|
|
||||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.48 -0.81,2.75 -2,3.45l1,1.74c1.79,-1.04 3,-2.97 3,-5.19zM12,3C6.48,3 2,7.48 2,13c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,18.53 4,15.96 4,13c0,-4.42 3.58,-8 8,-8s8,3.58 8,8c0,2.96 -1.61,5.53 -4,6.92l1,1.73c2.99,-1.73 5,-4.95 5,-8.65 0,-5.52 -4.48,-10 -10,-10z" />
|
|
||||||
</vector>
|
|
||||||
@@ -9,7 +9,11 @@
|
|||||||
android:id="@+id/errorIcon"
|
android:id="@+id/errorIcon"
|
||||||
android:layout_width="128dp"
|
android:layout_width="128dp"
|
||||||
android:layout_height="128dp"
|
android:layout_height="128dp"
|
||||||
android:layout_margin="8dp"
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
@@ -21,7 +25,11 @@
|
|||||||
android:id="@+id/errorTitle"
|
android:id="@+id/errorTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
android:text="@string/sorry"
|
android:text="@string/sorry"
|
||||||
android:textSize="@dimen/text_size_xlarge"
|
android:textSize="@dimen/text_size_xlarge"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -41,6 +49,6 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/errorTitle"
|
app:layout_constraintTop_toBottomOf="@+id/errorTitle"
|
||||||
tools:text="@string/startup_failed_service_error" />
|
tools:text="@string/qr_code_unsupported" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/errorIcon"
|
|
||||||
android:layout_width="32dp"
|
|
||||||
android:layout_height="32dp"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/errorMessageIntro"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/errorMessageIntro"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/alerts_and_states_error"
|
|
||||||
app:tint="@color/briar_red_500"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/errorMessageIntro"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:text="@string/hotspot_error_intro"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/errorIcon"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/errorMessageDetail"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginVertical="16dp"
|
|
||||||
android:background="@color/briar_orange_200"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:textColor="@color/briar_text_primary"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
android:typeface="monospace"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/errorMessageIntro"
|
|
||||||
tools:text="@string/hotspot_error_no_wifi_direct" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/feedbackButton"
|
|
||||||
style="@style/BriarButtonFlat.Positive"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/send_feedback"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/errorMessageDetail" />
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/fallbackFragment"
|
|
||||||
android:name="org.briarproject.briar.android.hotspot.FallbackFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/feedbackButton"
|
|
||||||
tools:layout="@layout/fragment_hotspot_save_apk" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/wifiTitleView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/hotspot_help_wifi_title"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/wifi1View"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:drawablePadding="6dp"
|
|
||||||
android:text="@string/hotspot_help_wifi_1"
|
|
||||||
app:drawableLeftCompat="@drawable/ic_circle_small"
|
|
||||||
app:drawableStartCompat="@drawable/ic_circle_small"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/wifiTitleView" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/wifi2View"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:drawablePadding="6dp"
|
|
||||||
android:text="@string/hotspot_help_wifi_2"
|
|
||||||
app:drawableLeftCompat="@drawable/ic_circle_small"
|
|
||||||
app:drawableStartCompat="@drawable/ic_circle_small"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/wifi1View" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/siteTitleView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/hotspot_help_site_title"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/wifi2View" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/site1View"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:drawablePadding="6dp"
|
|
||||||
android:text="@string/hotspot_help_site_1"
|
|
||||||
app:drawableLeftCompat="@drawable/ic_circle_small"
|
|
||||||
app:drawableStartCompat="@drawable/ic_circle_small"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/siteTitleView" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/site2View"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:drawablePadding="6dp"
|
|
||||||
android:text="@string/hotspot_help_site_2"
|
|
||||||
app:drawableLeftCompat="@drawable/ic_circle_small"
|
|
||||||
app:drawableStartCompat="@drawable/ic_circle_small"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/site1View" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/site3View"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:drawablePadding="6dp"
|
|
||||||
android:text="@string/hotspot_help_site_3"
|
|
||||||
app:drawableLeftCompat="@drawable/ic_circle_small"
|
|
||||||
app:drawableStartCompat="@drawable/ic_circle_small"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/site2View" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/site4View"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:drawablePadding="6dp"
|
|
||||||
android:text="@string/hotspot_help_site_4"
|
|
||||||
app:drawableLeftCompat="@drawable/ic_circle_small"
|
|
||||||
app:drawableStartCompat="@drawable/ic_circle_small"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/site3View" />
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/fallbackFragment"
|
|
||||||
android:name="org.briarproject.briar.android.hotspot.FallbackFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/site4View"
|
|
||||||
tools:layout="@layout/fragment_hotspot_save_apk" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<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"
|
|
||||||
tools:context=".android.hotspot.HotspotIntroFragment">
|
|
||||||
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView 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">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/manualIntroView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:text="@string/hotspot_manual_site"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/ssidLabelView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:text="@string/hotspot_manual_wifi_ssid"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/manualIntroView" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/ssidView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:background="@color/briar_primary"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:textColor="@color/briar_text_primary_inverse"
|
|
||||||
android:typeface="monospace"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/ssidLabelView"
|
|
||||||
tools:text="DIRECT-42-dfoln3lncsoij23" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/passwordLabelView"
|
|
||||||
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/enter_password"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/ssidView" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/passwordView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:background="@color/briar_primary"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:textColor="@color/briar_text_primary_inverse"
|
|
||||||
android:typeface="monospace"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/passwordLabelView"
|
|
||||||
tools:text="sdfsdgt2334rfw" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView 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">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/qrIntroView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:text="@string/hotspot_qr_wifi"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/cardView"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/cardView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
app:cardBackgroundColor="#ffffffff"
|
|
||||||
app:cardCornerRadius="16dp"
|
|
||||||
app:layout_constrainedHeight="true"
|
|
||||||
app:layout_constrainedWidth="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/qrIntroView"
|
|
||||||
app:layout_constraintVertical_bias="0.0">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/qrCodeView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:ignore="ContentDescription"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@id/fallbackTitleView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
android:text="@string/hotspot_help_fallback_title"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/fallbackIntro"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/hotspot_help_fallback_intro"
|
|
||||||
android:textSize="@dimen/text_size_medium"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/fallbackTitleView" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/fallbackButton"
|
|
||||||
style="@style/BriarButtonFlat.Positive"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/hotspot_help_fallback_button"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/fallbackIntro" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBar"
|
|
||||||
style="?android:progressBarStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="invisible"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/fallbackButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/fallbackButton" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<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">
|
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/connectedButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/tabLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_scrollFlags="scroll|enterAlways"
|
|
||||||
app:tabBackground="@color/briar_primary"
|
|
||||||
app:tabGravity="fill"
|
|
||||||
app:tabIconTint="@color/action_bar_text"
|
|
||||||
app:tabIndicatorColor="@color/briar_lime_400"
|
|
||||||
app:tabIndicatorHeight="4dp"
|
|
||||||
app:tabInlineLabel="true"
|
|
||||||
app:tabMaxWidth="0dp"
|
|
||||||
app:tabMode="fixed"
|
|
||||||
app:tabTextColor="@color/action_bar_text">
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabItem
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:icon="@drawable/forum_item_create_white"
|
|
||||||
android:text="@string/hotspot_tab_manual" />
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabItem
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:icon="@drawable/ic_qr_code"
|
|
||||||
android:text="@string/qr_code" />
|
|
||||||
</com.google.android.material.tabs.TabLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
|
||||||
android:id="@+id/pager"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
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>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/connectedButton"
|
|
||||||
style="@style/BriarButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:text="@string/hotspot_button_connected"
|
|
||||||
app:drawableLeftCompat="@drawable/ic_check_white"
|
|
||||||
app:drawableStartCompat="@drawable/ic_check_white"
|
|
||||||
app:drawableTint="@color/button_text"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/stopButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/stopButton"
|
|
||||||
style="@style/BriarButtonFlat.Negative"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:drawablePadding="8dp"
|
|
||||||
android:text="@string/hotspot_button_stop_sharing"
|
|
||||||
app:drawableLeftCompat="@drawable/ic_portable_wifi_off"
|
|
||||||
app:drawableStartCompat="@drawable/ic_portable_wifi_off"
|
|
||||||
app:drawableTint="@color/briar_red_500"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_help"
|
|
||||||
android:icon="@drawable/ic_help_outline_white"
|
|
||||||
android:title="@string/help"
|
|
||||||
app:showAsAction="always" />
|
|
||||||
|
|
||||||
</menu>
|
|
||||||
@@ -1,43 +1,111 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!--Setup-->
|
<!--Setup-->
|
||||||
<string name="setup_title">Добре дошли в Briar</string>
|
<string name="setup_title">Добре дошли в Briar</string>
|
||||||
<string name="setup_next">Следващ</string>
|
<string name="setup_name_explanation">Прякорът ви ще бъде видим до всяка ваша публикация. Няма да можете да го промените след като създадете профил.</string>
|
||||||
<string name="choose_nickname">Изберете име</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="choose_password">Изберете парола</string>
|
<string name="choose_password">Изберете парола</string>
|
||||||
<string name="confirm_password">Потвърдете парола</string>
|
<string name="confirm_password">Потвърдете парола</string>
|
||||||
<string name="name_too_long">Името е твърде дълго</string>
|
<string name="name_too_long">Името е твърде дълго</string>
|
||||||
<string name="password_too_weak">Паролата е твърде слаба</string>
|
<string name="password_too_weak">Паролата е твърде слаба</string>
|
||||||
<string name="passwords_do_not_match">Паролите не съвпадат</string>
|
<string name="passwords_do_not_match">Паролите не съвпадат</string>
|
||||||
<string name="create_account_button">Създаване на профил</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-->
|
<!--Login-->
|
||||||
<string name="enter_password">Парола</string>
|
<string name="enter_password">Парола</string>
|
||||||
<string name="try_again">Грешна парола, опитайте пак</string>
|
<string name="try_again">Грешна парола, опитайте отново</string>
|
||||||
<string name="sign_in_button">Вход</string>
|
<string name="dialog_title_cannot_check_password">Паролата не може да бъде проверена</string>
|
||||||
|
<string name="dialog_message_cannot_check_password">Briar не може да провери вашата парола. Рестартирате устройството, за да разрешите проблема.</string>
|
||||||
|
<string name="sign_in_button">Влизане</string>
|
||||||
<string name="forgotten_password">Забравена парола</string>
|
<string name="forgotten_password">Забравена парола</string>
|
||||||
<string name="dialog_title_lost_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_title">Briar не можа да стартира</string>
|
||||||
|
<string name="startup_failed_notification_text">Докоснете за повече информация.</string>
|
||||||
<string name="startup_failed_activity_title">Неуспешно стартиране</string>
|
<string name="startup_failed_activity_title">Неуспешно стартиране</string>
|
||||||
<string name="startup_failed_service_error">Briar не успя да стартира задължителен плъгин. Обикновено преинсталирането на Briar решава този проблем. Моля, имайте предвид, че ще изгубите профила си и всички данни, асоциирани с него, тъй като Briar не съхранява данните ви в централни сървъри.</string>
|
<string name="startup_failed_db_error">По някаква причина банката от данни на Briar е непоправимо повредена. Вашият профил, данни и всичките ви контакти са загубени. За жалост, се налага да преинсталирате Briar или да създадете нов профил, избирайки „Забравена парола“ от екрана за вход.</string>
|
||||||
<string name="expiry_date_reached">Софтуерът е невалиден.\nБлагодарим ви за тестването!</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>
|
||||||
<!--Navigation Drawer-->
|
<!--Navigation Drawer-->
|
||||||
<string name="nav_drawer_open_description">Отвори навигационно чекмедже</string>
|
<string name="nav_drawer_open_description">Отваря навигационната лента</string>
|
||||||
<string name="nav_drawer_close_description">Затвори навигационно чекмедже</string>
|
<string name="nav_drawer_close_description">Затваря навигационната лента</string>
|
||||||
<string name="contact_list_button">Контакти</string>
|
<string name="contact_list_button">Контакти</string>
|
||||||
<string name="groups_button">Частни групи</string>
|
<string name="groups_button">Частни групи</string>
|
||||||
<string name="forums_button">Форуми</string>
|
<string name="forums_button">Форуми</string>
|
||||||
<string name="blogs_button">Блогове</string>
|
<string name="blogs_button">Блогове</string>
|
||||||
<!--This is part of the main menu. The app will be locked when this is tapped.-->
|
<!--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="settings_button">Настройки</string>
|
||||||
<string name="sign_out_button">Отписване</string>
|
<string name="sign_out_button">Отписване</string>
|
||||||
<!--Transports-->
|
<string name="transports_onboarding_text">Докоснете, за да изберете как Briar да се свързва с контактите ви.</string>
|
||||||
|
<!--Transports: Tor-->
|
||||||
<string name="transport_tor">Интернет</string>
|
<string name="transport_tor">Интернет</string>
|
||||||
<string name="transport_bt">Bluetooth</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">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>
|
||||||
<!--Notifications-->
|
<!--Notifications-->
|
||||||
<string name="ongoing_notification_title">Вписан сте в Briar</string>
|
<string name="reminder_notification_title">Отписани сте от Briar</string>
|
||||||
<string name="ongoing_notification_text">Докоснете, за да отворите 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>
|
||||||
<plurals name="private_message_notification_text">
|
<plurals name="private_message_notification_text">
|
||||||
<item quantity="one">Ново лично съобщение.</item>
|
<item quantity="one">Ново лично съобщение.</item>
|
||||||
<item quantity="other">%d нови лични съобщения.</item>
|
<item quantity="other">%d нови лични съобщения.</item>
|
||||||
@@ -47,281 +115,543 @@
|
|||||||
<item quantity="other">%d нови групови съобщения.</item>
|
<item quantity="other">%d нови групови съобщения.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="forum_post_notification_text">
|
<plurals name="forum_post_notification_text">
|
||||||
<item quantity="one">Нова форумна публикация.</item>
|
<item quantity="one">Нова публикация във форум.</item>
|
||||||
<item quantity="other">%d нови форумни публикации.</item>
|
<item quantity="other">%d нови публикации във форуми.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="blog_post_notification_text">
|
<plurals name="blog_post_notification_text">
|
||||||
<item quantity="one">Нова блог публикация.</item>
|
<item quantity="one">Нова публикация в блог.</item>
|
||||||
<item quantity="other">%d нови блог публикации.</item>
|
<item quantity="other">%d нови публикации в блогове.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<!--Misc-->
|
<!--Misc-->
|
||||||
<string name="now">сега</string>
|
<string name="now">току-що</string>
|
||||||
<string name="show">Покажи</string>
|
<string name="show">Показване</string>
|
||||||
<string name="hide">Скрий</string>
|
<string name="hide">Скриване</string>
|
||||||
<string name="ok">ОК</string>
|
<string name="ok">Добре</string>
|
||||||
<string name="cancel">Отказ</string>
|
<string name="cancel">Отказ</string>
|
||||||
<string name="got_it">Разбрах</string>
|
<string name="got_it">Разбрах</string>
|
||||||
<string name="delete">Изтрий</string>
|
<string name="delete">Изтриване</string>
|
||||||
<string name="accept">Приеми</string>
|
<string name="accept">Приемане</string>
|
||||||
<string name="decline">Откажи</string>
|
<string name="decline">Отказване</string>
|
||||||
<string name="options">Опции</string>
|
<string name="online">На линия</string>
|
||||||
<string name="online">Онлайн</string>
|
<string name="offline">Извън линия</string>
|
||||||
<string name="offline">Офлайн</string>
|
<string name="send">Изпращане</string>
|
||||||
<string name="send">Изпрати</string>
|
<string name="allow">Разрешаване</string>
|
||||||
<string name="allow">Позволи</string>
|
<string name="open">Отваряне</string>
|
||||||
<string name="open">Отвори</string>
|
<string name="change">Променяне</string>
|
||||||
|
<string name="start">Старт</string>
|
||||||
<string name="no_data">Няма данни</string>
|
<string name="no_data">Няма данни</string>
|
||||||
<string name="ellipsis">...</string>
|
<string name="ellipsis">...</string>
|
||||||
<string name="text_too_long">Въведеният текст е твърде дълъг</string>
|
<string name="text_too_long">Въведеният текст е твърде дълъг</string>
|
||||||
<string name="show_onboarding">Показване на помощен диалог</string>
|
<string name="show_onboarding">Показване на помощен диалог</string>
|
||||||
|
<string name="fix">Поправяне</string>
|
||||||
<string name="help">Помощ</string>
|
<string name="help">Помощ</string>
|
||||||
|
<string name="sorry">Съжаляваме</string>
|
||||||
|
<string name="error_start_activity">Недостъпно на вашата система</string>
|
||||||
|
<string name="status_heading">Състояние</string>
|
||||||
<!--Contacts and Private Conversations-->
|
<!--Contacts and Private Conversations-->
|
||||||
|
<string name="no_contacts">Няма контакти</string>
|
||||||
|
<string name="no_contacts_action">Докоснете иконата с +, за да добавите контакти</string>
|
||||||
<string name="date_no_private_messages">Няма съобщения.</string>
|
<string name="date_no_private_messages">Няма съобщения.</string>
|
||||||
<string name="message_hint">Напиши съобщение</string>
|
<string name="no_private_messages">Няма съобщения</string>
|
||||||
<string name="delete_contact">Изтрий контакт</string>
|
<string name="message_hint">Съобщение</string>
|
||||||
<string name="dialog_title_delete_contact">Потвърди изтриването на контакт</string>
|
<string name="message_hint_auto_delete">Изчезващо съобщение</string>
|
||||||
<string name="dialog_message_delete_contact">Сигурни ли сте, че искате да изтриете този контакт и всички съобщения, обменени с този контакт?</string>
|
<string name="message_error">Грешка при изпращане на съобщение</string>
|
||||||
<string name="contact_deleted_toast">Контактът е изтрит</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>
|
||||||
<!--Adding Contacts-->
|
<!--Adding Contacts-->
|
||||||
<string name="add_contact_title">Добавяне на контакт</string>
|
<string name="add_contact_title">Добавяне на контакт на живо</string>
|
||||||
<string name="face_to_face">Трябва да се срещнете лично с човека, когото искате да добавите в Контакти.\n\nПо този начин никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
|
<string name="face_to_face">Трябва да се срещнете лично с човека, чиито контакт искате да добавите.\n\nТака никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
|
||||||
<string name="continue_button">Напред</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="waiting_for_contact_to_scan">Изчакване контактът да сканира и да се свърже\u2026</string>
|
||||||
<string name="exchanging_contact_details">Обмен на данни за контакт\u2026</string>
|
<string name="exchanging_contact_details">Обмяна на данни за контакт\u2026</string>
|
||||||
<string name="contact_added_toast">Добавен конктакт: %s</string>
|
<string name="contact_added_toast">Добавен контакт: %s</string>
|
||||||
<string name="contact_already_exists">Контактът %s вече съществува</string>
|
<string name="contact_already_exists">Контактът %s вече съществува</string>
|
||||||
<string name="qr_code_invalid">QR кодът е невалиден</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="connecting_to_device">Свързване с устройство\u2026</string>
|
<string name="connecting_to_device">Свързване с устройство\u2026</string>
|
||||||
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
|
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
|
||||||
<!--Introductions-->
|
<string name="connection_error_title">Не може да бъде установена връзка с контакта</string>
|
||||||
<string name="introduction_onboarding_title">Представете контактите си</string>
|
<string name="connection_error_feedback">Ако проблемът продължава, <a href="feedback">изпратете обратна връзка</a>, за да ни помогнете да подобрим приложението.</string>
|
||||||
<string name="introduction_onboarding_text">Можете да представите контактите си един на друг, за да не им се налага да се срещат лично, когато се свързват чрез Briar.</string>
|
<!--Adding Contacts Remotely-->
|
||||||
<string name="introduction_menu_item">Представи</string>
|
<string name="add_contact_remotely_title_case">Добавяне на контакт отдалечено</string>
|
||||||
<string name="introduction_activity_title">Избери контакт</string>
|
<string name="add_contact_nearby_title">Добавяне на контакт на живо</string>
|
||||||
<string name="introduction_message_title">Представи контакти</string>
|
<string name="add_contact_remotely_title">Добавяне на контакт отдалечено</string>
|
||||||
<string name="introduction_message_hint">Добавете съобщение (незадължително)</string>
|
<string name="contact_link_intro">Въведете препратката от вашия контакт</string>
|
||||||
<string name="introduction_button">Представи</string>
|
<string name="contact_link_hint">Препратка от контакт</string>
|
||||||
<string name="introduction_sent">Представянето ви е изпратено.</string>
|
<string name="paste_button">Поставяне</string>
|
||||||
<string name="introduction_error">Възникна грешка при представянето.</string>
|
<string name="add_contact_button">Добавяне на контакт</string>
|
||||||
<string name="introduction_response_error">Грешка при отговор на представянето</string>
|
<string name="copy_button">Копиране</string>
|
||||||
<string name="introduction_request_sent">Помолихте да представите %1$s на %2$s.</string>
|
<string name="share_button">Споделяне</string>
|
||||||
<string name="introduction_request_received">%1$s помоли да ви представи %2$s. Искате ли да добавите %2$s към контактите си?</string>
|
<string name="send_link_title">Размяна на препратки</string>
|
||||||
<string name="introduction_request_exists_received">%1$s помоли да ви представи на %2$s, но %2$s вече е в списъка ви с контакти. Тъй като %1$s може би не знае, все пак можете да отговорите:</string>
|
<string name="add_contact_choose_nickname">Избиране на прякор</string>
|
||||||
<string name="introduction_request_answered_received">%1$s помоли да ви представи на %2$s.</string>
|
<string name="add_contact_choose_a_nickname">Прякор</string>
|
||||||
<string name="introduction_response_accepted_sent">Приехте представянето на %1$s.</string>
|
<string name="nickname_intro">Изберете прякор на контакта. Той е видим само за вас.</string>
|
||||||
<string name="introduction_response_declined_sent">Отказахте представянето на %1$s.</string>
|
<string name="your_link">Споделете тази препратка с контакта, когото добавяте</string>
|
||||||
<string name="introduction_response_accepted_received">%1$s прие представянето на %2$s.</string>
|
<string name="link_clip_label">Препратка на Briar</string>
|
||||||
<string name="introduction_response_declined_received">%1$s отказа представянето на %2$s.</string>
|
<string name="link_copied_toast">Препратката е копирана</string>
|
||||||
<string name="introduction_response_declined_received_by_introducee">%1$s казва, че %2$s отказва представянето.</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">
|
<plurals name="contact_added_notification_text">
|
||||||
<item quantity="one">Добавен нов контакт.</item>
|
<item quantity="one">Добавен е нов контакт.</item>
|
||||||
<item quantity="other">%d добавени нови контакти.</item>
|
<item quantity="other">Добавени са %d нови контакта.</item>
|
||||||
</plurals>
|
</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_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>
|
||||||
<!--Private Groups-->
|
<!--Private Groups-->
|
||||||
<string name="groups_created_by">Създаден от %s</string>
|
<string name="groups_list_empty">Няма групи</string>
|
||||||
|
<string name="groups_list_empty_action">Докоснете иконата с +, за да създадете своя или поискайте от контактите си да споделят група с вас</string>
|
||||||
|
<string name="groups_created_by">Основател %s</string>
|
||||||
<plurals name="messages">
|
<plurals name="messages">
|
||||||
<item quantity="one">%d съобщение</item>
|
<item quantity="one">%d съобщение</item>
|
||||||
<item quantity="other">%d съобщения</item>
|
<item quantity="other">%d съобщения</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="groups_group_is_empty">Групата е празна</string>
|
<string name="groups_group_is_empty">Групата е празна</string>
|
||||||
<string name="groups_group_is_dissolved">Групата се е разпаднала</string>
|
<string name="groups_group_is_dissolved">Групата е разпусната</string>
|
||||||
<string name="groups_remove">Премахни</string>
|
<string name="groups_remove">Премахване</string>
|
||||||
<string name="groups_create_group_title">Създаване на група</string>
|
<string name="groups_create_group_title">Създаване на частна група</string>
|
||||||
<string name="groups_create_group_button">Създай група</string>
|
<string name="groups_create_group_button">Създаване на група</string>
|
||||||
<string name="groups_create_group_invitation_button">Изпрати покана</string>
|
<string name="groups_create_group_invitation_button">Изпращане на покана</string>
|
||||||
<string name="groups_create_group_hint">Изберете име за групата</string>
|
<string name="groups_create_group_hint">Име на частната група</string>
|
||||||
<string name="groups_invitation_sent">Поканата в група е изпратена</string>
|
<string name="groups_invitation_sent">Поканата за членство в група е изпратена</string>
|
||||||
<string name="groups_message_sent">Съобщението е изпратено</string>
|
|
||||||
<string name="groups_member_list">Списък с участници</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_you">Вие създадохте групата</string>
|
||||||
<string name="groups_member_created">%s създаде групата</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_member_joined">%s се включи в групата</string>
|
||||||
<string name="groups_leave">Напусни групата</string>
|
<string name="groups_leave">Напускане на групата</string>
|
||||||
<string name="groups_leave_dialog_title">Потвърждение на напускането</string>
|
<string name="groups_leave_dialog_title">Потвърждение на напускане</string>
|
||||||
<string name="groups_leave_dialog_message">Сигурни ли сте, че искате да напуснете тази група?</string>
|
<string name="groups_leave_dialog_message">Сигурни ли сте, че искате да напуснете тази група?</string>
|
||||||
<string name="groups_dissolve">Затвори групата</string>
|
<string name="groups_dissolve">Разпускане на група</string>
|
||||||
<string name="groups_dissolve_dialog_title">Потвърди затварянето на групата</string>
|
<string name="groups_dissolve_dialog_title">Потвърждение на разпускане на група</string>
|
||||||
<string name="groups_dissolve_dialog_message">Сигурни ли сте, че искате да затворите групата?\n\nВсички други участници няма да могат да продължат разговора и може да не получат най-новите съобщения. </string>
|
<string name="groups_dissolve_dialog_message">Сигурни ли сте, че искате да разпуснете групата?\n\nОстаналите членове няма да могат да продължат разговорите си и може да не получат последните съобщения.</string>
|
||||||
<string name="groups_dissolve_button">Затвори</string>
|
<string name="groups_dissolve_button">Разпускане</string>
|
||||||
<string name="groups_dissolved_dialog_title">Групата е затворена</string>
|
<string name="groups_dissolved_dialog_title">Разпусната група</string>
|
||||||
<string name="groups_dissolved_dialog_message">Групата е затворена от създателя.\n\nНе можете да пишете нови съобщения в груповия чат и може да не получите най-новите съобщения. </string>
|
<string name="groups_dissolved_dialog_message">Групата е разпусната от нейния основател.\n\nНе можете да изпращате съобщения и може да не сте получили всички изпратени до групата съобщения.</string>
|
||||||
<!--Private Group Invitations-->
|
<!--Private Group Invitations-->
|
||||||
<string name="groups_invitations_title">Покани в група</string>
|
<string name="groups_invitations_title">Покани за членство в група</string>
|
||||||
<string name="groups_invitations_invitation_sent">Поканихте %1$s в групата \"%2$s\"</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_invitation_received">%1$s ви покани в групата „%2$s“.</string>
|
||||||
<string name="groups_invitations_joined">Включихте се в групата</string>
|
<string name="groups_invitations_joined">Включихте се в групата</string>
|
||||||
<string name="groups_invitations_declined">Отказана покана в група</string>
|
<string name="groups_invitations_declined">Отказана покана за присъединяване в група</string>
|
||||||
<plurals name="groups_invitations_open">
|
<plurals name="groups_invitations_open">
|
||||||
<item quantity="one">%d отворена покана в група</item>
|
<item quantity="one">%d получена покана за членство в група</item>
|
||||||
<item quantity="other">%d отворени покани в група</item>
|
<item quantity="other">%d получени покани за членство в група</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="groups_invitations_response_accepted_sent">Приехте поканата в група на %s.</string>
|
<string name="groups_invitations_response_accepted_sent">Приехте поканата от %s за членство в група.</string>
|
||||||
<string name="groups_invitations_response_declined_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_auto">Поканата от %s за членство в група е отказана автоматично.</string>
|
||||||
<string name="groups_invitations_response_declined_received">%s отказа поканата в група. </string>
|
<string name="groups_invitations_response_accepted_received">%s прие поканата за членство в група.</string>
|
||||||
<string name="sharing_status_groups">Само създателят може да покани нови участници в групата. По-долу са изброени сегашните участници в групата.</string>
|
<string name="groups_invitations_response_declined_received">%s отказа поканата за членство в група. </string>
|
||||||
|
<string name="sharing_status_groups">Само основателят може да кани нови участници в групата. По-долу е списъкът с текущите участници.</string>
|
||||||
<!--Private Groups Revealing Contacts-->
|
<!--Private Groups Revealing Contacts-->
|
||||||
<string name="groups_reveal_contacts">Разкрий контакти</string>
|
<string name="groups_reveal_contacts">Разкриване на контакти</string>
|
||||||
<string name="groups_reveal_dialog_message">Може да изберете да разкриете контактите на всички сегашни и бъдещи участници в тази група.\n\nРазкриването на контактите прави връзката с групата по-бърза и сигурна, тъй като можете да общувате с разкрити контакти, дори когато създателят на групата е офлайн.</string>
|
<string name="groups_reveal_dialog_message">Можете да изберете дали да разкриете контактите на всички сегашни и бъдещи членове на групата.\n\nС разкриването им връзката с групата става по-бърза и надеждна, защото общувате с разкритите контакти, даже и основателят на групата да е извън мрежа.</string>
|
||||||
<string name="groups_reveal_visible">Връзка с контакта се вижда от групата</string>
|
<string name="groups_reveal_visible">Отношенията ви с контакта са видими за групата</string>
|
||||||
<string name="groups_reveal_visible_revealed_by_us">Връзка с контакта се вижда от групата (разкрита от вас)</string>
|
<string name="groups_reveal_visible_revealed_by_us">Отношенията ви с контакта са видими за групата (разкрити от вас)</string>
|
||||||
<string name="groups_reveal_visible_revealed_by_contact">Връзка с контакта се вижда от групата (разкрита от %s)</string>
|
<string name="groups_reveal_visible_revealed_by_contact">Отношенията ви с контакта са видими за групата (разкрити от %s)</string>
|
||||||
<string name="groups_reveal_invisible">Връзка с контакта не се вижда от групата</string>
|
<string name="groups_reveal_invisible">Отношенията ви с контакта не са видими за групата</string>
|
||||||
<!--Forums-->
|
<!--Forums-->
|
||||||
|
<string name="no_forums">Няма форуми</string>
|
||||||
|
<string name="no_forums_action">Докоснете иконата с +, за да създадете свой или поискайте от контактите си да споделят форум с вас</string>
|
||||||
<string name="create_forum_title">Създаване на форум</string>
|
<string name="create_forum_title">Създаване на форум</string>
|
||||||
<string name="choose_forum_hint">Изберете име за форума</string>
|
<string name="choose_forum_hint">Име на форума</string>
|
||||||
<string name="create_forum_button">Създай форум</string>
|
<string name="create_forum_button">Създаване на форум</string>
|
||||||
<string name="forum_created_toast">Форумът е създаден</string>
|
<string name="forum_created_toast">Форумът е създаден</string>
|
||||||
|
<string name="no_forum_posts">Няма публикации</string>
|
||||||
<string name="no_posts">Няма публикации</string>
|
<string name="no_posts">Няма публикации</string>
|
||||||
<plurals name="posts">
|
<plurals name="posts">
|
||||||
<item quantity="one">%d публикация</item>
|
<item quantity="one">%d публикация</item>
|
||||||
<item quantity="other">%d публикации</item>
|
<item quantity="other">%d публикации</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="forum_message_reply_hint">Нов отговор</string>
|
<string name="forum_new_message_hint">Публикация</string>
|
||||||
<string name="btn_reply">Отговори</string>
|
<string name="forum_message_reply_hint">Отговор</string>
|
||||||
<string name="forum_leave">Напусни форума</string>
|
<string name="btn_reply">Отговаряне</string>
|
||||||
<string name="dialog_title_leave_forum">Потвърдете напускането на форума</string>
|
<string name="forum_leave">Напускане на форума</string>
|
||||||
<string name="dialog_button_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>
|
||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_button">Сподели форум</string>
|
<string name="forum_share_button">Споделяне форума</string>
|
||||||
<string name="contacts_selected">Избрани контакти</string>
|
<string name="contacts_selected">Избрани контакти</string>
|
||||||
<string name="activity_share_toolbar_header">Избиране на контакти</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_shared_snackbar">Форумът е споделен с избраните контакти</string>
|
||||||
<string name="forum_share_message">Добавете съобщение (незадължително)</string>
|
<string name="forum_share_message">Добавете съобщение (незадължително)</string>
|
||||||
<string name="forum_share_error">Възникна грешка при споделянето на този форум.</string>
|
<string name="forum_share_error">Грешка при споделянето на форума.</string>
|
||||||
<string name="forum_invitation_received">%1$s сподели форума \"%2$s\" с вас.</string>
|
<string name="forum_invitation_received">%1$s сподели с вас форума „%2$s“.</string>
|
||||||
<string name="forum_invitation_sent">Споделихте форума \"%1$s\" с %2$s.</string>
|
<string name="forum_invitation_sent">Споделихте форума „%1$s“ с/ъс %2$s.</string>
|
||||||
<string name="forum_invitations_title">Покани във форум</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="shared_by_format">Споделен от %s</string>
|
<string name="shared_by_format">Споделен от %s</string>
|
||||||
<string name="forum_invitation_already_sharing">Вече е споделен</string>
|
<string name="forum_invitation_already_sharing">Вече е споделен</string>
|
||||||
<string name="forum_invitation_response_accepted_sent">Приехте поканата за форум на %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_declined_sent">Отказахте поканата на %s за членство във форум.</string>
|
||||||
|
<string name="forum_invitation_response_declined_auto">Поканата от %s за членство във форум е отказана автоматично.</string>
|
||||||
<string name="forum_invitation_response_accepted_received">%s прие поканата във форум.</string>
|
<string name="forum_invitation_response_accepted_received">%s прие поканата във форум.</string>
|
||||||
<string name="forum_invitation_response_declined_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="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">
|
<plurals name="forums_shared">
|
||||||
<item quantity="one">%d форум, споделен от контакти</item>
|
<item quantity="one">%d форум, споделен от контакти</item>
|
||||||
<item quantity="other">%d форума, споделени от контакти</item>
|
<item quantity="other">%d форума, споделени от контакти</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="nobody">Никого</string>
|
<string name="nobody">Никого</string>
|
||||||
<!--Blogs-->
|
<!--Blogs-->
|
||||||
<string name="read_more">прочети още</string>
|
<string name="blogs_other_blog_empty_state">Няма публикации</string>
|
||||||
<string name="blogs_write_blog_post">Нова блог публикация</string>
|
<string name="read_more">повече</string>
|
||||||
|
<string name="blogs_write_blog_post">Нова публикация в блога</string>
|
||||||
|
<string name="blogs_write_blog_post_body_hint">Въведете своята публикация</string>
|
||||||
<string name="blogs_publish_blog_post">Публикуване</string>
|
<string name="blogs_publish_blog_post">Публикуване</string>
|
||||||
<string name="blogs_blog_post_created">Блог публикацията е създадена</string>
|
<string name="blogs_blog_post_created">Публикацията в блога е създадена</string>
|
||||||
<string name="blogs_blog_post_received">Нова блог публикация</string>
|
<string name="blogs_blog_post_received">Получена е нова публикация в блога</string>
|
||||||
<string name="blogs_blog_post_scroll_to">Отвори</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_remove_blog">Премахване на блог</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_remove_blog_ok">Премахване</string>
|
||||||
|
<string name="blogs_blog_removed">Блогът е премахнат</string>
|
||||||
<string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string>
|
<string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string>
|
||||||
<string name="blogs_reblog_button">Реблог</string>
|
<string name="blogs_reblog_button">Препубликуване</string>
|
||||||
<!--Blog Sharing-->
|
<!--Blog Sharing-->
|
||||||
<string name="blogs_sharing_share">Споделяне на блог</string>
|
<string name="blogs_sharing_share">Споделяне на блог</string>
|
||||||
<string name="blogs_sharing_error">Възникна грешка при споделянето на този блог.</string>
|
<string name="blogs_sharing_error">Грешка при споделяне блога.</string>
|
||||||
<string name="blogs_sharing_button">Сподели блог</string>
|
<string name="blogs_sharing_button">Споделяне на блог</string>
|
||||||
<string name="blogs_sharing_snackbar">Блогът е споделен с избраните контакти</string>
|
<string name="blogs_sharing_snackbar">Блогът е споделен с избраните контакти</string>
|
||||||
<string name="blogs_sharing_response_accepted_sent">Приехте поканата в блог от %s.</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_sent">Отказахте поканата на %s за абонамент за блог.</string>
|
||||||
<string name="blogs_sharing_response_accepted_received">%s прие поканата в блог.</string>
|
<string name="blogs_sharing_response_declined_auto">Поканата на %s за абонамент за блог е отказана автоматично.</string>
|
||||||
<string name="blogs_sharing_response_declined_received">%s отказа поканата в блог.</string>
|
<string name="blogs_sharing_response_accepted_received">%s прие поканата за абонамент за блог.</string>
|
||||||
<string name="blogs_sharing_invitation_received">%1$s сподели блога \"%2$s\" с вас.</string>
|
<string name="blogs_sharing_response_declined_received">%s отказа поканата за абонамент за блог.</string>
|
||||||
<string name="blogs_sharing_invitation_sent">Споделихте блога \"%1$s\" с %2$s.</string>
|
<string name="blogs_sharing_invitation_received">%1$s сподели с вас блога „%2$s“.</string>
|
||||||
<string name="blogs_sharing_invitations_title">Блог покани</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="sharing_status_blog">Всеки абонат на блога може да го сподели с контактите си. Споделяте този блог със следните контакти. Възможно е да има и други, които не можете да видите.</string>
|
<string name="sharing_status_blog">Всеки абонат на блога може да го сподели с контактите си. Споделяте този блог със следните контакти. Възможно е да има и други, които не можете да видите.</string>
|
||||||
<!--RSS Feeds-->
|
<!--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_button">Внасяне</string>
|
||||||
<string name="blogs_rss_feeds_import_hint">Въведете URL адреса на RSS емисията</string>
|
<string name="blogs_rss_feeds_import_hint">Aдрес на емисия</string>
|
||||||
<string name="blogs_rss_feeds_import_error">Възникна грешка при внасянето на емисия.</string>
|
<string name="blogs_rss_feeds_import_error">Грешка при внасяне на емисията.</string>
|
||||||
|
<string name="blogs_rss_feeds_import_exists">Емисията вече е внесена.</string>
|
||||||
|
<string name="blogs_rss_feeds">Емисии на RSS</string>
|
||||||
<string name="blogs_rss_feeds_manage_imported">Внесена:</string>
|
<string name="blogs_rss_feeds_manage_imported">Внесена:</string>
|
||||||
<string name="blogs_rss_feeds_manage_author">Автор:</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">Премахване на емисия</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_remove_feed_ok">Премахване</string>
|
||||||
|
<string name="blogs_rss_feeds_manage_empty_state">Няма емисии на RSS\n\nДокоснете иконата с +, за да внесете емисия</string>
|
||||||
<string name="blogs_rss_feeds_manage_error">Възникна проблем при зареждането на емисиите ви. Моля, опитайте пак по-късно.</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-->
|
<!--Settings Display-->
|
||||||
<!--Settings Network-->
|
<string name="pref_language_title">Език и регион</string>
|
||||||
<string name="network_settings_title">Мрежа</string>
|
<string name="pref_language_changed">Тази настройка ще име ефект след рестарт на Briar. Отпишете се и рестартирайте Briar.</string>
|
||||||
<string name="bluetooth_setting">Свързване чрез Bluetooth</string>
|
<string name="pref_language_default">Спрямо системата</string>
|
||||||
<string name="bluetooth_setting_enabled">Когато контактите са наблизо</string>
|
<string name="display_settings_title">Външен вид</string>
|
||||||
<string name="bluetooth_setting_disabled">Само при добавяне на контакти</string>
|
<string name="pref_theme_title">Тема</string>
|
||||||
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
|
<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 Security and Panic-->
|
<!--Settings Security and Panic-->
|
||||||
<string name="security_settings_title">Сигурност</string>
|
<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-->
|
<!--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"-->
|
<!--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"-->
|
<!--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"-->
|
<!--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"-->
|
<!--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"-->
|
<!--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">Никога</string>
|
||||||
|
<string name="pref_lock_timeout_never_summary">Briar никога да не се заключва автоматично</string>
|
||||||
<string name="change_password">Промяна на парола</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="password_changed">Паролата е променена.</string>
|
||||||
<string name="panic_setting">Настройка на паник бутон</string>
|
<string name="panic_setting">Настройка на бутон за паника</string>
|
||||||
<string name="panic_setting_title">Паник бутон</string>
|
<string name="panic_setting_title">Бутон за паника</string>
|
||||||
<string name="panic_setting_hint">Конфигурация на приложение за паник бутон</string>
|
<string name="panic_setting_hint">Настройва се реакцията на Briar при използване на приложение за бутон за паника</string>
|
||||||
<string name="panic_app_setting_title">Приложение за паник бутон</string>
|
<string name="panic_app_setting_title">Приложение бутон за паника</string>
|
||||||
<string name="unknown_app">непознато приложение</string>
|
<string name="unknown_app">непознато приложение</string>
|
||||||
<string name="panic_app_setting_summary">Няма зададено приложение</string>
|
<string name="panic_app_setting_summary">Няма зададено приложение</string>
|
||||||
<string name="panic_app_setting_none">Няма</string>
|
<string name="panic_app_setting_none">Няма</string>
|
||||||
<string name="dialog_title_connect_panic_app">Потвърждение на паник приложение</string>
|
<string name="dialog_title_connect_panic_app">Потвърждение на приложение при паника</string>
|
||||||
<string name="dialog_message_connect_panic_app">Сигурни ли сте, че искате да позволите на %1$s да задейства унищожителни действия на паник бутон?</string>
|
<string name="dialog_message_connect_panic_app">Сигурни ли сте, че желаете да позволите на %1$s да задейства разрушителните действия на бутона за паника?</string>
|
||||||
|
<string name="panic_setting_destructive_action">Разрушителни действия</string>
|
||||||
<string name="panic_setting_signout_title">Отписване</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_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-->
|
<!--Settings Notifications-->
|
||||||
<string name="notification_settings_title">Известия</string>
|
<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_title">Лични съобщения</string>
|
||||||
<string name="notify_private_messages_setting_summary">Показвай известия за лични съобщения</string>
|
<string name="notify_private_messages_setting_summary">Известия за лични съобщения</string>
|
||||||
|
<string name="notify_private_messages_setting_summary_26">Настройки на известия за лични съобщения</string>
|
||||||
<string name="notify_group_messages_setting_title">Групови съобщения</string>
|
<string name="notify_group_messages_setting_title">Групови съобщения</string>
|
||||||
<string name="notify_group_messages_setting_summary">Показвай известия за групови съобщения</string>
|
<string name="notify_group_messages_setting_summary">Известия за групови съобщения</string>
|
||||||
<string name="notify_forum_posts_setting_title">Форумни публикации</string>
|
<string name="notify_group_messages_setting_summary_26">Настройки на известия за групови съобщения</string>
|
||||||
<string name="notify_forum_posts_setting_summary">Показвай известия за форумни публикации</string>
|
<string name="notify_forum_posts_setting_title">Публикации във форуми</string>
|
||||||
<string name="notify_blog_posts_setting_title">Блог публикации</string>
|
<string name="notify_forum_posts_setting_summary">Известия за публикации във форуми</string>
|
||||||
<string name="notify_blog_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_vibration_setting">Вибрация</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">Звук</string>
|
||||||
<string name="notify_sound_setting_default">Мелодия по подразбиране</string>
|
<string name="notify_sound_setting_default">Подразбиран тон на звънене</string>
|
||||||
<string name="notify_sound_setting_disabled">Никакви</string>
|
<string name="notify_sound_setting_disabled">Няма</string>
|
||||||
<string name="choose_ringtone_title">Изберете рингтон</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>
|
||||||
<!--Settings Feedback-->
|
<!--Settings Feedback-->
|
||||||
<string name="feedback_settings_title">Отзиви</string>
|
<string name="send_feedback">Изпращане на отзив</string>
|
||||||
<string name="send_feedback">Изпращане на отзиви</string>
|
|
||||||
<!--Link Warning-->
|
<!--Link Warning-->
|
||||||
<string name="link_warning_title">Предупреждение за линк</string>
|
<string name="link_warning_title">Предупреждение за препратка</string>
|
||||||
<string name="link_warning_intro">Линкът ще се отвори във външно приложение.</string>
|
<string name="link_warning_intro">Препратката ще бъде отворена от външно приложение.</string>
|
||||||
<string name="link_warning_text">Линкът може да се използва, за да ви идентифицира. Помислете дали имате доверие на човека, който ви изпраща линка, и обмислете дали да не го отворите с Orfox.</string>
|
<string name="link_warning_text">Така отворена може да бъде използвана, за да бъдете идентифицирани. Преценете дали имате доверие на подателя и обмислете дали да не я отворите с Tor Browser.</string>
|
||||||
<string name="link_warning_open_link">Отвори линк</string>
|
<string name="link_warning_open_link">Отваряне</string>
|
||||||
<!--Crash Reporter-->
|
<!--Crash Reporter-->
|
||||||
<string name="crash_report_title">Доклад на срив</string>
|
<string name="crash_report_title">Доклад на срив</string>
|
||||||
<string name="briar_crashed">Извинете, Briar се срина.</string>
|
<string name="briar_crashed">Извинете, Briar се срина.</string>
|
||||||
<string name="not_your_fault">Не е по ваша вина.</string>
|
<string name="not_your_fault">Не е по ваша вина.</string>
|
||||||
<string name="please_send_report">Моля, помогнете да изградим по-добър Briar, като ни изпратите доклад.</string>
|
<string name="please_send_report">Помогнете да направим Briar по-добър като ни изпратите доклад.</string>
|
||||||
<string name="report_is_encrypted">Гарантираме, че докладът е криптиран и изпратен безопасно.</string>
|
<string name="report_is_encrypted">Даваме обещание, че докладът е шифрован и е изпратен добре защитен.</string>
|
||||||
<string name="feedback_title">Отзиви</string>
|
<string name="feedback_title">Обратна връзка</string>
|
||||||
<string name="describe_crash">Опишете станалото (незадължително)</string>
|
<string name="describe_crash">Опишете случилото се (незадължително)</string>
|
||||||
<string name="enter_feedback">Въведете отзив</string>
|
<string name="enter_feedback">Въведете обратна връзка</string>
|
||||||
<string name="optional_contact_email">Имейл адресът ви (незадължително)</string>
|
<string name="optional_contact_email">Адрес на електронна поща (по желание)</string>
|
||||||
<string name="include_debug_report_crash">Добави анонимни данни за срива</string>
|
<string name="include_debug_report_crash">Изпращане на анонимни данни за срива</string>
|
||||||
<string name="include_debug_report_feedback">Добави анонимни данни за това устройствo</string>
|
<string name="include_debug_report_feedback">Изпращане на анонимни данни за устройството</string>
|
||||||
<string name="could_not_load_report_data">Данните за доклада не можаха да заредят.</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="send_report">Изпращане на доклад</string>
|
<string name="send_report">Изпращане на доклад</string>
|
||||||
<string name="close">Затваряне</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_saved">Докладът е запазен. Ще бъде изпратен при следващото влизане в Briar.</string>
|
||||||
|
<string name="dev_report_error">Грешка при изпращане на доклад</string>
|
||||||
<!--Sign Out-->
|
<!--Sign Out-->
|
||||||
<string name="progress_title_logout">Отписване от Briar...</string>
|
<string name="progress_title_logout">Отписване от Briar…</string>
|
||||||
<!--Screen Filters & Tapjacking-->
|
<!--Screen Filters & Tapjacking-->
|
||||||
<string name="screen_filter_title">Открит е овърлей на екрана</string>
|
<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>
|
||||||
<!--Permission Requests-->
|
<!--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-->
|
<!--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>
|
</resources>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<string name="confirm_password">Potwierdź hasło</string>
|
<string name="confirm_password">Potwierdź hasło</string>
|
||||||
<string name="name_too_long">Nazwa użytkownika jest zbyt długa</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="password_too_weak">Hasło jest zbyt długie</string>
|
||||||
<string name="passwords_do_not_match">Hasła się nie zgadzają</string>
|
<string name="passwords_do_not_match">Hasła różnią się</string>
|
||||||
<string name="create_account_button">Utwórz Konto</string>
|
<string name="create_account_button">Utwórz Konto</string>
|
||||||
<string name="more_info">Więcej Informacji</string>
|
<string name="more_info">Więcej Informacji</string>
|
||||||
<string name="don_t_ask_again">Nie pytaj ponownie</string>
|
<string name="don_t_ask_again">Nie pytaj ponownie</string>
|
||||||
@@ -27,10 +27,11 @@
|
|||||||
<!--Login-->
|
<!--Login-->
|
||||||
<string name="enter_password">Hasło</string>
|
<string name="enter_password">Hasło</string>
|
||||||
<string name="try_again">Złe hasło, spróbuj ponownie</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="sign_in_button">Zaloguj Się</string>
|
||||||
<string name="forgotten_password">Zapomniałem hasło</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_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_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_notification_text">Naciśnij, aby uzyskać więcej informacji</string>
|
||||||
<string name="startup_failed_activity_title">Briar nie mógł się uruchomić</string>
|
<string name="startup_failed_activity_title">Briar nie mógł się uruchomić</string>
|
||||||
@@ -45,7 +46,9 @@
|
|||||||
<item quantity="other">To jest testowa wersja Briar. Twoje konto wygaśnie za %d dni i nie będzie odnowione.</item>
|
<item quantity="other">To jest testowa wersja Briar. Twoje konto wygaśnie za %d dni i nie będzie odnowione.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="expiry_date_reached">Ten program wygasł.\nDziękujemy za testy!</string>
|
<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="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_open_database">Deszyfruję Bazę Danych...</string>
|
||||||
<string name="startup_migrate_database">Aktualizuję Bazę Danych...</string>
|
<string name="startup_migrate_database">Aktualizuję Bazę Danych...</string>
|
||||||
<string name="startup_compact_database">Kompaktowanie Bazy Danych…</string>
|
<string name="startup_compact_database">Kompaktowanie Bazy Danych…</string>
|
||||||
@@ -60,12 +63,16 @@
|
|||||||
<string name="lock_button">Zablokuj Aplikację</string>
|
<string name="lock_button">Zablokuj Aplikację</string>
|
||||||
<string name="settings_button">Ustawienia</string>
|
<string name="settings_button">Ustawienia</string>
|
||||||
<string name="sign_out_button">Wyloguj się</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-->
|
<!--Transports: Tor-->
|
||||||
<string name="transport_tor">Internet</string>
|
<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-->
|
<!--Transports: Wi-Fi-->
|
||||||
<string name="transport_lan">Wi-Fi</string>
|
<string name="transport_lan">Wi-Fi</string>
|
||||||
<!--Transports: Bluetooth-->
|
<!--Transports: Bluetooth-->
|
||||||
<string name="transport_bt">Bluetooth</string>
|
<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-->
|
<!--Notifications-->
|
||||||
<string name="reminder_notification_title">Wylogowano z Briar</string>
|
<string name="reminder_notification_title">Wylogowano z Briar</string>
|
||||||
<string name="reminder_notification_text">Dotknij, aby zalogować się ponownie</string>
|
<string name="reminder_notification_text">Dotknij, aby zalogować się ponownie</string>
|
||||||
@@ -128,6 +135,7 @@
|
|||||||
<string name="date_no_private_messages">Brak wiadomości.</string>
|
<string name="date_no_private_messages">Brak wiadomości.</string>
|
||||||
<string name="no_private_messages">Brak wiadomości do pokazania</string>
|
<string name="no_private_messages">Brak wiadomości do pokazania</string>
|
||||||
<string name="message_hint">Nowa wiadomość</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_caption_hint">Dodaj podpis (opcjonalne)</string>
|
||||||
<string name="image_attach">Załącz obraz</string>
|
<string name="image_attach">Załącz obraz</string>
|
||||||
<string name="image_attach_error">Nie udało się dołączyć obrazu(ów)</string>
|
<string name="image_attach_error">Nie udało się dołączyć obrazu(ów)</string>
|
||||||
@@ -135,12 +143,18 @@
|
|||||||
<string name="image_attach_error_invalid_mime_type">Format obrazu jest nieobsługiwany: %s</string>
|
<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">Zmień nazwę kontaktu</string>
|
||||||
<string name="set_contact_alias_hint">Nazwa 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="menu_item_connect_via_bluetooth">Połącz przez Bluetooth</string>
|
||||||
<string name="dialog_title_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 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 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 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."-->
|
<!--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="dialog_title_not_all_messages_deleted">Nie mogłem usunąć wszystkich wiadomości</string>
|
||||||
<string name="delete_contact">Usuń kontakt</string>
|
<string name="delete_contact">Usuń kontakt</string>
|
||||||
<string name="dialog_title_delete_contact">Potwierdź usunięcie kontaktu</string>
|
<string name="dialog_title_delete_contact">Potwierdź usunięcie kontaktu</string>
|
||||||
@@ -173,7 +187,7 @@
|
|||||||
<string name="connecting_to_device">Łączenie z urządzeniem\u2026</string>
|
<string name="connecting_to_device">Łączenie z urządzeniem\u2026</string>
|
||||||
<string name="authenticating_with_device">Autoryzowanie 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_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ć zgłoszenie</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ć opinię</a> aby pomóc nam ulepszyć aplikację.</string>
|
||||||
<!--Adding Contacts Remotely-->
|
<!--Adding Contacts Remotely-->
|
||||||
<string name="add_contact_remotely_title_case">Dodaj Kontakt na odległość</string>
|
<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>
|
<string name="add_contact_nearby_title">Dodaj kontakt w pobliżu</string>
|
||||||
@@ -401,6 +415,8 @@
|
|||||||
<string name="blogs_rss_feeds_import_button">Zaimportuj</string>
|
<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_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_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_imported">Zaimportowane:</string>
|
||||||
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
||||||
<string name="blogs_rss_feeds_manage_updated">Ostatnio Zaktualizowane:</string>
|
<string name="blogs_rss_feeds_manage_updated">Ostatnio Zaktualizowane:</string>
|
||||||
@@ -410,6 +426,7 @@
|
|||||||
<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_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>
|
<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-->
|
<!--Settings Profile Picture-->
|
||||||
|
<string name="dialog_confirm_profile_picture_title">Zmień zdjęcie profilowe</string>
|
||||||
<!--Settings Display-->
|
<!--Settings Display-->
|
||||||
<string name="pref_language_title">Język & region</string>
|
<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>
|
<string name="pref_language_changed">Te ustawienia zostaną zastosowane gdy zrestartujesz Briar. Proszę się wylogować i zrestartować Briar.</string>
|
||||||
@@ -422,6 +439,7 @@
|
|||||||
<string name="pref_theme_system">Domyślny systemu</string>
|
<string name="pref_theme_system">Domyślny systemu</string>
|
||||||
<!--Settings Connections-->
|
<!--Settings Connections-->
|
||||||
<string name="network_settings_title">Połączenia</string>
|
<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>
|
<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)"-->
|
<!--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>
|
<string name="tor_network_setting_summary">Automatycznie: %1$s (za %2$s)</string>
|
||||||
@@ -489,6 +507,7 @@
|
|||||||
<string name="choose_ringtone_title">Wybierz dzwonek</string>
|
<string name="choose_ringtone_title">Wybierz dzwonek</string>
|
||||||
<string name="cannot_load_ringtone">Nie mogę załadować dzwonka</string>
|
<string name="cannot_load_ringtone">Nie mogę załadować dzwonka</string>
|
||||||
<!--Conversation Settings-->
|
<!--Conversation Settings-->
|
||||||
|
<string name="disappearing_messages_title">Znikające wiadomości</string>
|
||||||
<string name="learn_more">Dowiedz się więcej</string>
|
<string name="learn_more">Dowiedz się więcej</string>
|
||||||
<!--Settings Feedback-->
|
<!--Settings Feedback-->
|
||||||
<string name="send_feedback">Wyślij opinię</string>
|
<string name="send_feedback">Wyślij opinię</string>
|
||||||
@@ -505,7 +524,7 @@
|
|||||||
<string name="report_is_encrypted">Obiecujemy, że raport z awarii jest zaszyfrowany i wysyłany bezpiecznie.</string>
|
<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="feedback_title">Twoja opinia</string>
|
||||||
<string name="describe_crash">Opisz co się stało (opcjonalne)</string>
|
<string name="describe_crash">Opisz co się stało (opcjonalne)</string>
|
||||||
<string name="enter_feedback">Wprowadź swoje uwagi</string>
|
<string name="enter_feedback">Napisz swoją opinię</string>
|
||||||
<string name="optional_contact_email">Twój adres email (opcjonalne)</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_crash">Załącz anonimowe dane na temat awarii</string>
|
||||||
<string name="include_debug_report_feedback">Załącz anonimowe dane o tym urządzeniu</string>
|
<string name="include_debug_report_feedback">Załącz anonimowe dane o tym urządzeniu</string>
|
||||||
@@ -515,6 +534,8 @@
|
|||||||
<string name="dev_report_connectivity">Łączenie</string>
|
<string name="dev_report_connectivity">Łączenie</string>
|
||||||
<string name="send_report">Wyślij raport</string>
|
<string name="send_report">Wyślij raport</string>
|
||||||
<string name="close">Zamknij</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>
|
<string name="dev_report_saved">Raport zapisany. Zostanie wysłany następnym razem kiedy zalogujesz się do Briar.</string>
|
||||||
<!--Sign Out-->
|
<!--Sign Out-->
|
||||||
<string name="progress_title_logout">Wylogowywanie z Briar...</string>
|
<string name="progress_title_logout">Wylogowywanie z Briar...</string>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
<color name="briar_blue_600">#1b69b6</color>
|
<color name="briar_blue_600">#1b69b6</color>
|
||||||
<color name="briar_blue_400">#418cd8</color>
|
<color name="briar_blue_400">#418cd8</color>
|
||||||
|
|
||||||
<color name="briar_orange_200">#fed69f</color>
|
|
||||||
<color name="briar_orange_500">#fc9403</color>
|
<color name="briar_orange_500">#fc9403</color>
|
||||||
|
|
||||||
<color name="briar_red_500">#db3b21</color>
|
<color name="briar_red_500">#db3b21</color>
|
||||||
|
|||||||
@@ -46,14 +46,12 @@
|
|||||||
<string name="forgotten_password">I have forgotten my password</string>
|
<string name="forgotten_password">I have forgotten my password</string>
|
||||||
<string name="dialog_title_lost_password">Lost 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="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_activity_title">Briar Startup Failure</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_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">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_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. 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_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">This version of the app is too old. Please upgrade to the latest version and try again.</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 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>
|
<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>
|
||||||
<plurals name="expiry_warning">
|
<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="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>
|
<item quantity="other">This is a test version of Briar. Your account will expire in %d days and cannot be renewed.</item>
|
||||||
@@ -162,7 +160,6 @@
|
|||||||
<string name="sorry">Sorry</string>
|
<string name="sorry">Sorry</string>
|
||||||
<string name="error_start_activity">Unavailable on your system</string>
|
<string name="error_start_activity">Unavailable on your system</string>
|
||||||
<string name="status_heading">Status:</string>
|
<string name="status_heading">Status:</string>
|
||||||
<string name="error">Error</string>
|
|
||||||
|
|
||||||
<!-- Contacts and Private Conversations-->
|
<!-- Contacts and Private Conversations-->
|
||||||
<string name="no_contacts">No contacts to show</string>
|
<string name="no_contacts">No contacts to show</string>
|
||||||
@@ -618,8 +615,7 @@
|
|||||||
<string name="learn_more">Learn more</string>
|
<string name="learn_more">Learn more</string>
|
||||||
<string name="disappearing_messages_summary">Make future messages in this conversation automatically disappear after 7\u00A0days.</string>
|
<string name="disappearing_messages_summary">Make future messages in this conversation automatically disappear after 7\u00A0days.</string>
|
||||||
|
|
||||||
<!-- Settings Actions -->
|
<!-- Settings Feedback -->
|
||||||
<string name="pref_category_actions">Actions</string>
|
|
||||||
<string name="send_feedback">Send feedback</string>
|
<string name="send_feedback">Send feedback</string>
|
||||||
|
|
||||||
<!-- Link Warning -->
|
<!-- Link Warning -->
|
||||||
@@ -703,7 +699,7 @@
|
|||||||
<string name="removable_drive_title_receive">Receive data</string>
|
<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_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_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 which does not yet support this feature.</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_button">Choose file for export</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_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>
|
<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>
|
||||||
@@ -718,72 +714,6 @@
|
|||||||
<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>
|
<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 -->
|
<!-- Screenshots -->
|
||||||
|
|
||||||
<!-- This is a name to be used in screenshots. Feel free to change it to a local name. -->
|
<!-- This is a name to be used in screenshots. Feel free to change it to a local name. -->
|
||||||
|
|||||||
@@ -24,24 +24,10 @@
|
|||||||
app:fragment="org.briarproject.briar.android.settings.NotificationsFragment"
|
app:fragment="org.briarproject.briar.android.settings.NotificationsFragment"
|
||||||
app:icon="@drawable/ic_notifications" />
|
app:icon="@drawable/ic_notifications" />
|
||||||
|
|
||||||
<PreferenceCategory
|
<Preference
|
||||||
android:key="pref_key_actions"
|
android:key="pref_key_send_feedback"
|
||||||
android:layout="@layout/preferences_category"
|
android:title="@string/send_feedback"
|
||||||
android:title="@string/pref_category_actions"
|
app:icon="@drawable/ic_feedback" />
|
||||||
app:allowDividerAbove="true">
|
|
||||||
<Preference
|
|
||||||
android:key="pref_key_share_app"
|
|
||||||
android:title="@string/hotspot_title"
|
|
||||||
app:icon="@drawable/ic_settings_share">
|
|
||||||
<intent
|
|
||||||
android:targetClass="org.briarproject.briar.android.hotspot.HotspotActivity"
|
|
||||||
android:targetPackage="@string/app_package" />
|
|
||||||
</Preference>
|
|
||||||
<Preference
|
|
||||||
android:key="pref_key_send_feedback"
|
|
||||||
android:title="@string/send_feedback"
|
|
||||||
app:icon="@drawable/ic_feedback" />
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="pref_key_dev"
|
android:key="pref_key_dev"
|
||||||
|
|||||||
@@ -225,7 +225,6 @@ dependencyVerification {
|
|||||||
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
||||||
'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
|
'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
|
||||||
'org.mockito:mockito-core:3.9.0:mockito-core-3.9.0.jar:a1f64211407b8dc4cf80b16e07cc11aa9e5228d53dc4a5357326d66825f6a4ac',
|
'org.mockito:mockito-core:3.9.0:mockito-core-3.9.0.jar:a1f64211407b8dc4cf80b16e07cc11aa9e5228d53dc4a5357326d66825f6a4ac',
|
||||||
'org.nanohttpd:nanohttpd:2.3.1:nanohttpd-2.3.1.jar:de864c47818157141a24c9acb36df0c47d7bf15b7ff48c90610f3eb4e5df0e58',
|
|
||||||
'org.objenesis:objenesis:3.2:objenesis-3.2.jar:03d960bd5aef03c653eb000413ada15eb77cdd2b8e4448886edf5692805e35f3',
|
'org.objenesis:objenesis:3.2:objenesis-3.2.jar:03d960bd5aef03c653eb000413ada15eb77cdd2b8e4448886edf5692805e35f3',
|
||||||
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
|
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
|
||||||
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
|
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
|
||||||
|
|||||||
@@ -97,6 +97,5 @@ internal class HeadlessModule(private val appDir: File) {
|
|||||||
override fun shouldEnableDisappearingMessages() = false
|
override fun shouldEnableDisappearingMessages() = false
|
||||||
override fun shouldEnableConnectViaBluetooth() = false
|
override fun shouldEnableConnectViaBluetooth() = false
|
||||||
override fun shouldEnableTransferData() = false
|
override fun shouldEnableTransferData() = false
|
||||||
override fun shouldEnableShareAppViaOfflineHotspot() = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user