mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
132 Commits
attachment
...
beta-1.2.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75dfa80541 | ||
|
|
41b59fbcfe | ||
|
|
98a4f5def1 | ||
|
|
aeefa35f38 | ||
|
|
4e7f33edfd | ||
|
|
f1e957ffed | ||
|
|
9e3fed6bc0 | ||
|
|
bf9a39cc6c | ||
|
|
72aa5397f8 | ||
|
|
21eaab3259 | ||
|
|
92d595da35 | ||
|
|
5e85566fc3 | ||
|
|
1574bf35fc | ||
|
|
533e01e881 | ||
|
|
383367f0c8 | ||
|
|
ca052ea7dd | ||
|
|
5147f6b7e6 | ||
|
|
84a8ff1dd8 | ||
|
|
6c489fbea3 | ||
|
|
c7200910c9 | ||
|
|
663e5c4b46 | ||
|
|
529eaceec7 | ||
|
|
f516dbe34f | ||
|
|
5b515d7e18 | ||
|
|
ef04a26cfc | ||
|
|
2e6fe42074 | ||
|
|
124e2f99b0 | ||
|
|
190a6bff96 | ||
|
|
01df141c08 | ||
|
|
d7c9bf80de | ||
|
|
3a5e51e248 | ||
|
|
a76e3dcec1 | ||
|
|
0fdc7199ed | ||
|
|
248f482fee | ||
|
|
4196d046a3 | ||
|
|
722ebb22f6 | ||
|
|
a4f561ca1a | ||
|
|
c7db0bf6fa | ||
|
|
ca6f458551 | ||
|
|
c85990408a | ||
|
|
3ed0204170 | ||
|
|
e2b3340734 | ||
|
|
78aac8de52 | ||
|
|
971ae3a20e | ||
|
|
622e7a775a | ||
|
|
103e8482b0 | ||
|
|
ddcfc11012 | ||
|
|
ab2e40abde | ||
|
|
1ddceaadd6 | ||
|
|
7a644f7d8b | ||
|
|
397afbfec0 | ||
|
|
0d4cb05ac0 | ||
|
|
aa0937e6aa | ||
|
|
4bf8d4c0e7 | ||
|
|
75fcd28071 | ||
|
|
5f29ab3b40 | ||
|
|
f45d00e23c | ||
|
|
2b589c2da6 | ||
|
|
67d15ec82e | ||
|
|
2d44d749ba | ||
|
|
6ef86c5638 | ||
|
|
131f9b9696 | ||
|
|
a876d4cfb7 | ||
|
|
fafcacf808 | ||
|
|
7a0d990f0b | ||
|
|
234bdf686e | ||
|
|
edb9da107f | ||
|
|
d1d4914c6a | ||
|
|
9261d23bba | ||
|
|
f4febe90c9 | ||
|
|
ecd766b204 | ||
|
|
ca4fc2dc26 | ||
|
|
c3ddcdffe0 | ||
|
|
2e37619357 | ||
|
|
c247d745df | ||
|
|
3a4de3d2cb | ||
|
|
04f1036dbf | ||
|
|
9736f9d31f | ||
|
|
440d5239b1 | ||
|
|
e4a8b10b94 | ||
|
|
41676065c5 | ||
|
|
1fcc83a0d0 | ||
|
|
249b85cd26 | ||
|
|
a23e0699d8 | ||
|
|
e3e47dae48 | ||
|
|
9660ff2fff | ||
|
|
ea810c817b | ||
|
|
876d50975e | ||
|
|
bf5bdc52b4 | ||
|
|
29320c410e | ||
|
|
d41472a18c | ||
|
|
c411065255 | ||
|
|
3ac5646355 | ||
|
|
c46fdce277 | ||
|
|
643ef593e1 | ||
|
|
eda17449be | ||
|
|
28f82a1507 | ||
|
|
8734825346 | ||
|
|
640f3d63b0 | ||
|
|
b1dfd867f0 | ||
|
|
ff76900d74 | ||
|
|
945fdb8ee4 | ||
|
|
53fe3e1592 | ||
|
|
be76c5b7db | ||
|
|
909e946e58 | ||
|
|
408d9ddee4 | ||
|
|
0e5027e725 | ||
|
|
2d4c97a69e | ||
|
|
7d62ae5fa8 | ||
|
|
bd616853cf | ||
|
|
32e1d6c748 | ||
|
|
6b022afa67 | ||
|
|
e8b454b25b | ||
|
|
54c05b5ffe | ||
|
|
d145a082f5 | ||
|
|
4fd012c31a | ||
|
|
95d06770bf | ||
|
|
428247b7b2 | ||
|
|
a921361a56 | ||
|
|
fe7dfa721e | ||
|
|
92eb06a9e9 | ||
|
|
5beed1a748 | ||
|
|
774047d856 | ||
|
|
fc28e7aa88 | ||
|
|
78459499b2 | ||
|
|
c2973608d7 | ||
|
|
be1c33cb42 | ||
|
|
c955466bda | ||
|
|
593a0c4632 | ||
|
|
ed20b2d8d6 | ||
|
|
9ab9e02f8a | ||
|
|
3f70ae3c8c |
80
.idea/codeStyles/Project.xml
generated
80
.idea/codeStyles/Project.xml
generated
@@ -1,16 +1,7 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<option name="RIGHT_MARGIN" value="100" />
|
|
||||||
<AndroidXmlCodeStyleSettings>
|
|
||||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
|
||||||
</AndroidXmlCodeStyleSettings>
|
|
||||||
<JavaCodeStyleSettings>
|
<JavaCodeStyleSettings>
|
||||||
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
|
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
|
||||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
|
||||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
|
||||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
|
||||||
<value />
|
|
||||||
</option>
|
|
||||||
<option name="IMPORT_LAYOUT_TABLE">
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
<value>
|
<value>
|
||||||
<package name="android" withSubpackages="true" static="false" />
|
<package name="android" withSubpackages="true" static="false" />
|
||||||
@@ -77,7 +68,6 @@
|
|||||||
</indentOptions>
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="XML">
|
<codeStyleSettings language="XML">
|
||||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
@@ -90,7 +80,8 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>xmlns:android</NAME>
|
<NAME>xmlns:android</NAME>
|
||||||
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
</rule>
|
</rule>
|
||||||
@@ -100,7 +91,8 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>xmlns:.*</NAME>
|
<NAME>xmlns:.*</NAME>
|
||||||
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
<order>BY_NAME</order>
|
<order>BY_NAME</order>
|
||||||
@@ -111,6 +103,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*:id</NAME>
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -121,6 +114,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*:name</NAME>
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -131,6 +125,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>name</NAME>
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -141,6 +136,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>style</NAME>
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -151,6 +147,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -161,64 +158,12 @@
|
|||||||
<rule>
|
<rule>
|
||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*:layout_width</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
</rule>
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:layout_height</NAME>
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:layout_.*</NAME>
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:width</NAME>
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:height</NAME>
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
</rule>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
@@ -226,6 +171,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ android {
|
|||||||
buildToolsVersion '28.0.3'
|
buildToolsVersion '28.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 16
|
||||||
targetSdkVersion 26
|
targetSdkVersion 28
|
||||||
versionCode 10107
|
versionCode 10204
|
||||||
versionName "1.1.7"
|
versionName "1.2.4"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
@@ -30,8 +30,8 @@ configurations {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':bramble-core', configuration: 'default')
|
implementation project(path: ':bramble-core', configuration: 'default')
|
||||||
tor 'org.briarproject:tor-android:0.3.5.8@zip'
|
tor 'org.briarproject:tor-android:0.3.5.8-64@zip'
|
||||||
tor 'org.briarproject:obfs4proxy-android:0.0.9@zip'
|
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
|
||||||
|
|
||||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
|
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
|
||||||
|
|
||||||
@@ -59,6 +59,8 @@ task unpackTorBinaries {
|
|||||||
copy {
|
copy {
|
||||||
from configurations.tor.collect { zipTree(it) }
|
from configurations.tor.collect { zipTree(it) }
|
||||||
into torBinariesDir
|
into torBinariesDir
|
||||||
|
// TODO: Remove after next Tor upgrade, which won't include non-PIE binaries
|
||||||
|
include 'geoip.zip', '*_pie.zip'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependsOn cleanTorBinaries
|
dependsOn cleanTorBinaries
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
@@ -89,9 +88,15 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
// Check that we have a Tor binary for this architecture
|
// Check that we have a Tor binary for this architecture
|
||||||
String architecture = null;
|
String architecture = null;
|
||||||
for (String abi : AndroidUtils.getSupportedArchitectures()) {
|
for (String abi : AndroidUtils.getSupportedArchitectures()) {
|
||||||
if (abi.startsWith("x86")) {
|
if (abi.startsWith("x86_64")) {
|
||||||
|
architecture = "x86_64";
|
||||||
|
break;
|
||||||
|
} else if (abi.startsWith("x86")) {
|
||||||
architecture = "x86";
|
architecture = "x86";
|
||||||
break;
|
break;
|
||||||
|
} else if (abi.startsWith("arm64")) {
|
||||||
|
architecture = "arm64";
|
||||||
|
break;
|
||||||
} else if (abi.startsWith("armeabi")) {
|
} else if (abi.startsWith("armeabi")) {
|
||||||
architecture = "arm";
|
architecture = "arm";
|
||||||
break;
|
break;
|
||||||
@@ -101,8 +106,8 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
LOG.info("Tor is not supported on this architecture");
|
LOG.info("Tor is not supported on this architecture");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Use position-independent executable for SDK >= 16
|
// Use position-independent executable
|
||||||
if (Build.VERSION.SDK_INT >= 16) architecture += "_pie";
|
architecture += "_pie";
|
||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.content.Context.WIFI_SERVICE;
|
import static android.content.Context.WIFI_SERVICE;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.provider.Settings.Secure.ANDROID_ID;
|
import static android.provider.Settings.Secure.ANDROID_ID;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -74,8 +75,7 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
|
|||||||
// Silence strict mode
|
// Silence strict mode
|
||||||
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskWrites();
|
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskWrites();
|
||||||
super.writeSeed();
|
super.writeSeed();
|
||||||
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
|
if (SDK_INT <= 18) applyOpenSslFix();
|
||||||
applyOpenSslFix();
|
|
||||||
StrictMode.setThreadPolicy(tp);
|
StrictMode.setThreadPolicy(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ dependencyVerification {
|
|||||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||||
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
||||||
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
||||||
'org.briarproject:obfs4proxy-android:0.0.9:obfs4proxy-android-0.0.9.zip:9b7e9181535ea8d8bbe8ae6338e08cf4c5fc1e357a779393e0ce49586d459ae0',
|
'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
|
||||||
'org.briarproject:tor-android:0.3.5.8:tor-android-0.3.5.8.zip:42a13a6f185be1a62f42e3f30ce66a3c099ac5ec890a65e7593111b65b44a54a',
|
'org.briarproject:tor-android:0.3.5.8-64:tor-android-0.3.5.8-64.zip:9f144088c0fe845d1cf3232cdc2b51c68e6f9a22660592009f43a5633fca8824',
|
||||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||||
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
|
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ public interface FeatureFlags {
|
|||||||
|
|
||||||
boolean shouldEnableImageAttachments();
|
boolean shouldEnableImageAttachments();
|
||||||
|
|
||||||
boolean shouldEnableRemoteContacts();
|
boolean shouldEnablePrivateMessageDeletion();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import org.briarproject.bramble.api.FormatException;
|
|||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
|
import org.briarproject.bramble.api.db.PendingContactExistsException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
@@ -117,9 +119,14 @@ public interface ContactManager {
|
|||||||
* @throws FormatException If the link is invalid
|
* @throws FormatException If the link is invalid
|
||||||
* @throws GeneralSecurityException If the pending contact's handshake
|
* @throws GeneralSecurityException If the pending contact's handshake
|
||||||
* public key is invalid
|
* public key is invalid
|
||||||
|
* @throws ContactExistsException If a contact with the same handshake
|
||||||
|
* public key already exists
|
||||||
|
* @throws PendingContactExistsException If a pending contact with the same
|
||||||
|
* handshake public key already exists
|
||||||
*/
|
*/
|
||||||
PendingContact addPendingContact(String link, String alias)
|
PendingContact addPendingContact(String link, String alias)
|
||||||
throws DbException, FormatException, GeneralSecurityException;
|
throws DbException, FormatException, GeneralSecurityException,
|
||||||
|
ContactExistsException, PendingContactExistsException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pending contact with the given ID.
|
* Returns the pending contact with the given ID.
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
/**
|
/**
|
||||||
* Stores a pending contact.
|
* Stores a pending contact.
|
||||||
*/
|
*/
|
||||||
void addPendingContact(Transaction txn, PendingContact p)
|
void addPendingContact(Transaction txn, PendingContact p, AuthorId local)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when a duplicate pending contact is added to the database. This
|
* Thrown when a duplicate pending contact is added to the database. This
|
||||||
* exception may occur due to concurrent updates and does not indicate a
|
* exception may occur due to concurrent updates and does not indicate a
|
||||||
* database error.
|
* database error.
|
||||||
*/
|
*/
|
||||||
public class PendingContactExistsException extends DbException {
|
public class PendingContactExistsException extends DbException {
|
||||||
|
|
||||||
|
private final PendingContact pendingContact;
|
||||||
|
|
||||||
|
public PendingContactExistsException(PendingContact pendingContact) {
|
||||||
|
this.pendingContact = pendingContact;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PendingContact getPendingContact() {
|
||||||
|
return pendingContact;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
|||||||
pendingContactFactory.createPendingContact(link, alias);
|
pendingContactFactory.createPendingContact(link, alias);
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.addPendingContact(txn, p);
|
AuthorId local = identityManager.getLocalAuthor(txn).getId();
|
||||||
|
db.addPendingContact(txn, p, local);
|
||||||
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
|
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
|
||||||
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
|
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
|
||||||
ourKeyPair);
|
ourKeyPair);
|
||||||
|
|||||||
@@ -267,6 +267,16 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Collection<ContactId> getContacts(T txn, AuthorId local) throws DbException;
|
Collection<ContactId> getContacts(T txn, AuthorId local) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contact with the given {@code handshakePublicKey}
|
||||||
|
* for the given local pseudonym or {@code null} if none exists.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Contact getContact(T txn, PublicKey handshakePublicKey, AuthorId local)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the group with the given ID.
|
* Returns the group with the given ID.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
|||||||
@@ -291,12 +291,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addPendingContact(Transaction transaction, PendingContact p)
|
public void addPendingContact(Transaction transaction, PendingContact p,
|
||||||
throws DbException {
|
AuthorId local) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (db.containsPendingContact(txn, p.getId()))
|
Contact contact = db.getContact(txn, p.getPublicKey(), local);
|
||||||
throw new PendingContactExistsException();
|
if (contact != null)
|
||||||
|
throw new ContactExistsException(local, contact.getAuthor());
|
||||||
|
if (db.containsPendingContact(txn, p.getId())) {
|
||||||
|
PendingContact existing = db.getPendingContact(txn, p.getId());
|
||||||
|
throw new PendingContactExistsException(existing);
|
||||||
|
}
|
||||||
db.addPendingContact(txn, p);
|
db.addPendingContact(txn, p);
|
||||||
transaction.attach(new PendingContactAddedEvent(p));
|
transaction.attach(new PendingContactAddedEvent(p));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1465,6 +1465,47 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Contact getContact(Connection txn, PublicKey handshakePublicKey,
|
||||||
|
AuthorId localAuthorId) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT contactId, authorId, formatVersion, name,"
|
||||||
|
+ " alias, publicKey, verified"
|
||||||
|
+ " FROM contacts"
|
||||||
|
+ " WHERE handshakePublicKey = ? AND localAuthorId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, handshakePublicKey.getEncoded());
|
||||||
|
ps.setBytes(2, localAuthorId.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) {
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ContactId contactId = new ContactId(rs.getInt(1));
|
||||||
|
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
||||||
|
int formatVersion = rs.getInt(3);
|
||||||
|
String name = rs.getString(4);
|
||||||
|
String alias = rs.getString(5);
|
||||||
|
PublicKey publicKey = new SignaturePublicKey(rs.getBytes(6));
|
||||||
|
boolean verified = rs.getBoolean(7);
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
Author author =
|
||||||
|
new Author(authorId, formatVersion, name, publicKey);
|
||||||
|
return new Contact(contactId, author, localAuthorId, alias,
|
||||||
|
handshakePublicKey, verified);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group getGroup(Connection txn, GroupId g) throws DbException {
|
public Group getGroup(Connection txn, GroupId g) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
|
|||||||
@@ -195,8 +195,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (!assetsAreUpToDate()) installAssets();
|
if (!assetsAreUpToDate()) installAssets();
|
||||||
if (cookieFile.exists() && !cookieFile.delete())
|
if (cookieFile.exists() && !cookieFile.delete())
|
||||||
LOG.warning("Old auth cookie not deleted");
|
LOG.warning("Old auth cookie not deleted");
|
||||||
// Migrate old settings before having a chance to stop
|
|
||||||
migrateSettings();
|
|
||||||
// Start a new Tor process
|
// Start a new Tor process
|
||||||
LOG.info("Starting Tor");
|
LOG.info("Starting Tor");
|
||||||
String torPath = torFile.getAbsolutePath();
|
String torPath = torFile.getAbsolutePath();
|
||||||
@@ -816,21 +814,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove when sufficient time has passed. Added 2018-08-15
|
|
||||||
private void migrateSettings() {
|
|
||||||
Settings sOld = callback.getSettings();
|
|
||||||
int oldNetwork = sOld.getInt("network", -1);
|
|
||||||
if (oldNetwork == -1) return;
|
|
||||||
Settings s = new Settings();
|
|
||||||
if (oldNetwork == 0) {
|
|
||||||
s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_NEVER);
|
|
||||||
} else if (oldNetwork == 1) {
|
|
||||||
s.putBoolean(PREF_TOR_MOBILE, false);
|
|
||||||
}
|
|
||||||
s.putInt("network", -1);
|
|
||||||
callback.mergeSettings(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ConnectionStatus {
|
private static class ConnectionStatus {
|
||||||
|
|
||||||
// All of the following are locking: this
|
// All of the following are locking: this
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.briarproject.bramble.test.BrambleTestCase;
|
|||||||
import org.briarproject.bramble.test.SettableClock;
|
import org.briarproject.bramble.test.SettableClock;
|
||||||
import org.briarproject.bramble.test.TestDatabaseConfig;
|
import org.briarproject.bramble.test.TestDatabaseConfig;
|
||||||
import org.briarproject.bramble.test.TestMessageFactory;
|
import org.briarproject.bramble.test.TestMessageFactory;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -1149,6 +1150,43 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetContactsByHandshakePublicKey() throws Exception {
|
||||||
|
Database<Connection> db = open(false);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Add an identity for a local author - no contacts should be
|
||||||
|
// associated
|
||||||
|
db.addIdentity(txn, identity);
|
||||||
|
PublicKey handshakePublicKey = TestUtils.getSignaturePublicKey();
|
||||||
|
Contact contact =
|
||||||
|
db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||||
|
assertNull(contact);
|
||||||
|
|
||||||
|
// Add a contact associated with the local author
|
||||||
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
|
handshakePublicKey, true));
|
||||||
|
contact = db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||||
|
assertNotNull(contact);
|
||||||
|
assertEquals(contactId, contact.getId());
|
||||||
|
assertEquals(author, contact.getAuthor());
|
||||||
|
assertNull(contact.getAlias());
|
||||||
|
assertEquals(handshakePublicKey, contact.getHandshakePublicKey());
|
||||||
|
assertTrue(contact.isVerified());
|
||||||
|
assertEquals(author.getName(), contact.getAuthor().getName());
|
||||||
|
assertEquals(author.getPublicKey(), contact.getAuthor().getPublicKey());
|
||||||
|
assertEquals(author.getFormatVersion(),
|
||||||
|
contact.getAuthor().getFormatVersion());
|
||||||
|
|
||||||
|
// Ensure no contacts are returned after contact was deleted
|
||||||
|
db.removeContact(txn, contactId);
|
||||||
|
contact = db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||||
|
assertNull(contact);
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOfferedMessages() throws Exception {
|
public void testOfferedMessages() throws Exception {
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class BrambleCoreIntegrationTestModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldEnableRemoteContacts() {
|
public boolean shouldEnablePrivateMessageDeletion() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN
|
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN, zh-Hant: zh-rTW
|
||||||
|
|
||||||
[briar.stringsxml-5]
|
[briar.stringsxml-5]
|
||||||
file_filter = src/main/res/values-<lang>/strings.xml
|
file_filter = src/main/res/values-<lang>/strings.xml
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ android {
|
|||||||
buildToolsVersion '28.0.3'
|
buildToolsVersion '28.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 15
|
minSdkVersion 16
|
||||||
targetSdkVersion 26
|
targetSdkVersion 28
|
||||||
versionCode 10107
|
versionCode 10204
|
||||||
versionName "1.1.7"
|
versionName "1.2.4"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
buildConfigField "String", "GitHash",
|
buildConfigField "String", "GitHash",
|
||||||
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
|
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
|
||||||
@@ -117,7 +117,7 @@ dependencies {
|
|||||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||||
implementation 'com.google.zxing:core:3.3.3'
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0'
|
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0'
|
||||||
implementation 'com.vanniktech:emoji-google:0.5.1'
|
implementation 'com.vanniktech:emoji-google:0.6.0' // later versions already use androidx
|
||||||
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1' // later versions already use androidx
|
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1' // later versions already use androidx
|
||||||
def glideVersion = '4.9.0'
|
def glideVersion = '4.9.0'
|
||||||
implementation("com.github.bumptech.glide:glide:$glideVersion") {
|
implementation("com.github.bumptech.glide:glide:$glideVersion") {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
# QR codes
|
# QR codes
|
||||||
-keep class com.google.zxing.Result
|
-keep class com.google.zxing.Result
|
||||||
|
-keepclassmembers enum * {
|
||||||
|
public static **[] values();
|
||||||
|
public static ** valueOf(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
# RSS libraries
|
# RSS libraries
|
||||||
-keep,includedescriptorclasses class com.rometools.rome.feed.synd.impl.** { *; }
|
-keep,includedescriptorclasses class com.rometools.rome.feed.synd.impl.** { *; }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
|
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -13,6 +14,7 @@ import dagger.Component;
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = {
|
@Component(modules = {
|
||||||
AppModule.class,
|
AppModule.class,
|
||||||
|
AttachmentModule.class,
|
||||||
BriarCoreModule.class,
|
BriarCoreModule.class,
|
||||||
BrambleAndroidModule.class,
|
BrambleAndroidModule.class,
|
||||||
BriarAccountModule.class,
|
BriarAccountModule.class,
|
||||||
|
|||||||
@@ -47,15 +47,17 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
);
|
);
|
||||||
private final MessageId msgId = new MessageId(getRandomId());
|
private final MessageId msgId = new MessageId(getRandomId());
|
||||||
|
|
||||||
|
private final ImageHelper imageHelper = new ImageHelperImpl();
|
||||||
private final AttachmentRetriever retriever =
|
private final AttachmentRetriever retriever =
|
||||||
new AttachmentRetriever(null, dimensions);
|
new AttachmentRetrieverImpl(null, dimensions, imageHelper,
|
||||||
|
new ImageSizeCalculator(imageHelper));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSmallJpegImage() throws Exception {
|
public void testSmallJpegImage() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(smallKitten);
|
InputStream is = getUrlInputStream(smallKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(160, item.getWidth());
|
assertEquals(160, item.getWidth());
|
||||||
assertEquals(240, item.getHeight());
|
assertEquals(240, item.getHeight());
|
||||||
@@ -70,8 +72,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testBigJpegImage() throws Exception {
|
public void testBigJpegImage() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(originalKitten);
|
InputStream is = getUrlInputStream(originalKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(1728, item.getWidth());
|
assertEquals(1728, item.getWidth());
|
||||||
assertEquals(2592, item.getHeight());
|
assertEquals(2592, item.getHeight());
|
||||||
@@ -86,8 +88,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testSmallPngImage() throws Exception {
|
public void testSmallPngImage() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||||
InputStream is = getUrlInputStream(pngKitten);
|
InputStream is = getUrlInputStream(pngKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(737, item.getWidth());
|
assertEquals(737, item.getWidth());
|
||||||
assertEquals(510, item.getHeight());
|
assertEquals(510, item.getHeight());
|
||||||
@@ -102,8 +104,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testUberGif() throws Exception {
|
public void testUberGif() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(uberGif);
|
InputStream is = getUrlInputStream(uberGif);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -117,8 +119,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testLottaPixels() throws Exception {
|
public void testLottaPixels() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(lottaPixel);
|
InputStream is = getUrlInputStream(lottaPixel);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(64250, item.getWidth());
|
assertEquals(64250, item.getWidth());
|
||||||
assertEquals(64250, item.getHeight());
|
assertEquals(64250, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -132,8 +134,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testImageIoCrash() throws Exception {
|
public void testImageIoCrash() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(imageIoCrash);
|
InputStream is = getUrlInputStream(imageIoCrash);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1184, item.getWidth());
|
assertEquals(1184, item.getWidth());
|
||||||
assertEquals(448, item.getHeight());
|
assertEquals(448, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -147,8 +149,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testGimpCrash() throws Exception {
|
public void testGimpCrash() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(gimpCrash);
|
InputStream is = getUrlInputStream(gimpCrash);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -162,8 +164,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testOptiPngAfl() throws Exception {
|
public void testOptiPngAfl() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(optiPngAfl);
|
InputStream is = getUrlInputStream(optiPngAfl);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(32, item.getWidth());
|
assertEquals(32, item.getWidth());
|
||||||
assertEquals(32, item.getHeight());
|
assertEquals(32, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -177,8 +179,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testLibrawError() throws Exception {
|
public void testLibrawError() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(librawError);
|
InputStream is = getUrlInputStream(librawError);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertTrue(item.hasError());
|
assertTrue(item.hasError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +188,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testSmallAnimatedGifMaxDimensions() throws Exception {
|
public void testSmallAnimatedGifMaxDimensions() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("animated.gif");
|
InputStream is = getAssetInputStream("animated.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(65535, item.getWidth());
|
assertEquals(65535, item.getWidth());
|
||||||
assertEquals(65535, item.getHeight());
|
assertEquals(65535, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -201,8 +203,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testSmallAnimatedGifHugeDimensions() throws Exception {
|
public void testSmallAnimatedGifHugeDimensions() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("animated2.gif");
|
InputStream is = getAssetInputStream("animated2.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(10000, item.getWidth());
|
assertEquals(10000, item.getWidth());
|
||||||
assertEquals(10000, item.getHeight());
|
assertEquals(10000, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -216,8 +218,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testSmallGifLargeDimensions() throws Exception {
|
public void testSmallGifLargeDimensions() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("error_large.gif");
|
InputStream is = getAssetInputStream("error_large.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(16384, item.getWidth());
|
assertEquals(16384, item.getWidth());
|
||||||
assertEquals(16384, item.getHeight());
|
assertEquals(16384, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -231,8 +233,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testHighError() throws Exception {
|
public void testHighError() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getAssetInputStream("error_high.jpg");
|
InputStream is = getAssetInputStream("error_high.jpg");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(10000, item.getHeight());
|
assertEquals(10000, item.getHeight());
|
||||||
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
||||||
@@ -246,8 +248,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testWideError() throws Exception {
|
public void testWideError() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getAssetInputStream("error_wide.jpg");
|
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1920, item.getWidth());
|
assertEquals(1920, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
android:label="@string/crash_report_title"
|
android:label="@string/crash_report_title"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:theme="@style/BriarTheme.NoActionBar"
|
android:theme="@style/BriarTheme.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden">
|
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.account.SetupActivity"
|
android:name="org.briarproject.briar.android.account.SetupActivity"
|
||||||
android:label="@string/setup_title"
|
android:label="@string/setup_title"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||||
android:theme="@style/BriarTheme.NoActionBar"
|
android:theme="@style/BriarTheme.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
android:windowSoftInputMode="adjustResize|stateUnchanged">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
||||||
android:label="@string/groups_create_group_title"
|
android:label="@string/groups_create_group_title"
|
||||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||||
@@ -174,8 +174,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity"
|
android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity"
|
||||||
android:label="@string/groups_member_list"
|
android:label="@string/groups_member_list"
|
||||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity">
|
||||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
|
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
|
||||||
@@ -184,8 +183,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity"
|
android:name="org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity"
|
||||||
android:label="@string/groups_reveal_contacts"
|
android:label="@string/groups_reveal_contacts"
|
||||||
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
|
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity">
|
||||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
|
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
|
||||||
@@ -223,7 +221,7 @@
|
|||||||
android:name="org.briarproject.briar.android.forum.CreateForumActivity"
|
android:name="org.briarproject.briar.android.forum.CreateForumActivity"
|
||||||
android:label="@string/create_forum_title"
|
android:label="@string/create_forum_title"
|
||||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||||
@@ -292,7 +290,7 @@
|
|||||||
android:name="org.briarproject.briar.android.blog.WriteBlogPostActivity"
|
android:name="org.briarproject.briar.android.blog.WriteBlogPostActivity"
|
||||||
android:label="@string/blogs_write_blog_post"
|
android:label="@string/blogs_write_blog_post"
|
||||||
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize">
|
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
|
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
|
||||||
@@ -302,7 +300,7 @@
|
|||||||
android:name="org.briarproject.briar.android.blog.ReblogActivity"
|
android:name="org.briarproject.briar.android.blog.ReblogActivity"
|
||||||
android:label="@string/blogs_reblog_button"
|
android:label="@string/blogs_reblog_button"
|
||||||
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
|
||||||
android:windowSoftInputMode="stateHidden">
|
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
|
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
|
||||||
@@ -312,7 +310,7 @@
|
|||||||
android:name="org.briarproject.briar.android.blog.RssFeedImportActivity"
|
android:name="org.briarproject.briar.android.blog.RssFeedImportActivity"
|
||||||
android:label="@string/blogs_rss_feeds_import"
|
android:label="@string/blogs_rss_feeds_import"
|
||||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize">
|
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
|
||||||
@@ -341,7 +339,7 @@
|
|||||||
android:name="org.briarproject.briar.android.introduction.IntroductionActivity"
|
android:name="org.briarproject.briar.android.introduction.IntroductionActivity"
|
||||||
android:label="@string/introduction_activity_title"
|
android:label="@string/introduction_activity_title"
|
||||||
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
|
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
|
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
|
||||||
@@ -369,7 +367,8 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.login.ChangePasswordActivity"
|
android:name="org.briarproject.briar.android.login.ChangePasswordActivity"
|
||||||
android:label="@string/change_password"
|
android:label="@string/change_password"
|
||||||
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
|
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
|
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
|
||||||
@@ -424,7 +423,7 @@
|
|||||||
android:name=".android.contact.add.remote.AddContactActivity"
|
android:name=".android.contact.add.remote.AddContactActivity"
|
||||||
android:label="@string/add_contact_remotely_title_case"
|
android:label="@string/add_contact_remotely_title_case"
|
||||||
android:theme="@style/BriarTheme"
|
android:theme="@style/BriarTheme"
|
||||||
android:windowSoftInputMode="stateHidden|adjustResize"/>
|
android:windowSoftInputMode="adjustResize|stateHidden"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".android.contact.add.remote.PendingContactListActivity"
|
android:name=".android.contact.add.remote.PendingContactListActivity"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||||
import org.briarproject.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||||
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||||
@@ -68,7 +69,8 @@ import dagger.Component;
|
|||||||
BriarCoreModule.class,
|
BriarCoreModule.class,
|
||||||
BrambleAndroidModule.class,
|
BrambleAndroidModule.class,
|
||||||
BriarAccountModule.class,
|
BriarAccountModule.class,
|
||||||
AppModule.class
|
AppModule.class,
|
||||||
|
AttachmentModule.class
|
||||||
})
|
})
|
||||||
public interface AndroidComponent
|
public interface AndroidComponent
|
||||||
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
||||||
|
|||||||
@@ -347,8 +347,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
if (currentTime - lastSound > SOUND_DELAY) {
|
if (currentTime - lastSound > SOUND_DELAY) {
|
||||||
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
|
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
|
||||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||||
if (sound && !StringUtils.isNullOrEmpty(ringtoneUri))
|
if (sound && !StringUtils.isNullOrEmpty(ringtoneUri)) {
|
||||||
b.setSound(Uri.parse(ringtoneUri));
|
Uri uri = Uri.parse(ringtoneUri);
|
||||||
|
if (!"file".equals(uri.getScheme())) b.setSound(uri);
|
||||||
|
}
|
||||||
b.setDefaults(getDefaults());
|
b.setDefaults(getDefaults());
|
||||||
lastSound = currentTime;
|
lastSound = currentTime;
|
||||||
}
|
}
|
||||||
@@ -359,7 +361,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
int defaults = DEFAULT_LIGHTS;
|
int defaults = DEFAULT_LIGHTS;
|
||||||
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
|
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
|
||||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||||
if (sound && StringUtils.isNullOrEmpty(ringtoneUri))
|
if (sound && (StringUtils.isNullOrEmpty(ringtoneUri) ||
|
||||||
|
"file".equals(Uri.parse(ringtoneUri).getScheme())))
|
||||||
defaults |= DEFAULT_SOUND;
|
defaults |= DEFAULT_SOUND;
|
||||||
if (settings.getBoolean(PREF_NOTIFY_VIBRATION, true))
|
if (settings.getBoolean(PREF_NOTIFY_VIBRATION, true))
|
||||||
defaults |= DEFAULT_VIBRATE;
|
defaults |= DEFAULT_VIBRATE;
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ public class AppModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldEnableRemoteContacts() {
|
public boolean shouldEnablePrivateMessageDeletion() {
|
||||||
return IS_DEBUG_BUILD;
|
return IS_DEBUG_BUILD;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package org.briarproject.briar.android;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Application.ActivityLifecycleCallbacks;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class BackgroundMonitor implements ActivityLifecycleCallbacks {
|
|
||||||
|
|
||||||
private final AtomicInteger foregroundActivities = new AtomicInteger(0);
|
|
||||||
|
|
||||||
boolean isRunningInBackground() {
|
|
||||||
return foregroundActivities.get() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Activity a, @Nullable Bundle state) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityStarted(Activity a) {
|
|
||||||
foregroundActivities.incrementAndGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResumed(Activity a) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityPaused(Activity a) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityStopped(Activity a) {
|
|
||||||
foregroundActivities.decrementAndGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivitySaveInstanceState(Activity a,
|
|
||||||
@Nullable Bundle outState) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityDestroyed(Activity a) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,9 +34,9 @@ import java.util.logging.LogRecord;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
|
||||||
import static java.util.logging.Level.FINE;
|
import static java.util.logging.Level.FINE;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.acra.ReportField.ANDROID_VERSION;
|
import static org.acra.ReportField.ANDROID_VERSION;
|
||||||
import static org.acra.ReportField.APP_VERSION_CODE;
|
import static org.acra.ReportField.APP_VERSION_CODE;
|
||||||
import static org.acra.ReportField.APP_VERSION_NAME;
|
import static org.acra.ReportField.APP_VERSION_NAME;
|
||||||
@@ -82,10 +82,9 @@ public class BriarApplicationImpl extends Application
|
|||||||
implements BriarApplication {
|
implements BriarApplication {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BriarApplicationImpl.class.getName());
|
getLogger(BriarApplicationImpl.class.getName());
|
||||||
|
|
||||||
private final CachingLogHandler logHandler = new CachingLogHandler();
|
private final CachingLogHandler logHandler = new CachingLogHandler();
|
||||||
private final BackgroundMonitor backgroundMonitor = new BackgroundMonitor();
|
|
||||||
|
|
||||||
private AndroidComponent applicationComponent;
|
private AndroidComponent applicationComponent;
|
||||||
private volatile SharedPreferences prefs;
|
private volatile SharedPreferences prefs;
|
||||||
@@ -108,12 +107,16 @@ public class BriarApplicationImpl extends Application
|
|||||||
|
|
||||||
if (IS_DEBUG_BUILD) enableStrictMode();
|
if (IS_DEBUG_BUILD) enableStrictMode();
|
||||||
|
|
||||||
Logger rootLogger = Logger.getLogger("");
|
Logger rootLogger = getLogger("");
|
||||||
if (!IS_DEBUG_BUILD && !IS_BETA_BUILD) {
|
Handler[] handlers = rootLogger.getHandlers();
|
||||||
// Remove default log handlers so system log is not used
|
// Disable the Android logger for release builds
|
||||||
for (Handler handler : rootLogger.getHandlers()) {
|
for (Handler handler : handlers) rootLogger.removeHandler(handler);
|
||||||
rootLogger.removeHandler(handler);
|
if (IS_DEBUG_BUILD || IS_BETA_BUILD) {
|
||||||
}
|
// We can't set the level of the Android logger at runtime, so
|
||||||
|
// raise records to the logger's default level
|
||||||
|
rootLogger.addHandler(new LevelRaisingHandler(FINE, INFO));
|
||||||
|
// Restore the default handlers after the level raising handler
|
||||||
|
for (Handler handler : handlers) rootLogger.addHandler(handler);
|
||||||
}
|
}
|
||||||
rootLogger.addHandler(logHandler);
|
rootLogger.addHandler(logHandler);
|
||||||
rootLogger.setLevel(IS_DEBUG_BUILD || IS_BETA_BUILD ? FINE : INFO);
|
rootLogger.setLevel(IS_DEBUG_BUILD || IS_BETA_BUILD ? FINE : INFO);
|
||||||
@@ -122,9 +125,6 @@ public class BriarApplicationImpl extends Application
|
|||||||
|
|
||||||
applicationComponent = createApplicationComponent();
|
applicationComponent = createApplicationComponent();
|
||||||
EmojiManager.install(new GoogleEmojiProvider());
|
EmojiManager.install(new GoogleEmojiProvider());
|
||||||
|
|
||||||
if (SDK_INT < 16)
|
|
||||||
registerActivityLifecycleCallbacks(backgroundMonitor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AndroidComponent createApplicationComponent() {
|
protected AndroidComponent createApplicationComponent() {
|
||||||
@@ -186,12 +186,8 @@ public class BriarApplicationImpl extends Application
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRunningInBackground() {
|
public boolean isRunningInBackground() {
|
||||||
if (SDK_INT >= 16) {
|
RunningAppProcessInfo info = new RunningAppProcessInfo();
|
||||||
RunningAppProcessInfo info = new RunningAppProcessInfo();
|
ActivityManager.getMyMemoryState(info);
|
||||||
ActivityManager.getMyMemoryState(info);
|
return (info.importance != IMPORTANCE_FOREGROUND);
|
||||||
return (info.importance != IMPORTANCE_FOREGROUND);
|
|
||||||
} else {
|
|
||||||
return backgroundMonitor.isRunningInBackground();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,8 +238,6 @@ public class BriarService extends Service {
|
|||||||
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
|
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
|
||||||
LOG.info("Trim memory: running low");
|
LOG.info("Trim memory: running low");
|
||||||
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
|
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||||
// This level may be received if SDK_INT < 16, although the
|
|
||||||
// constant isn't declared until API level 16
|
|
||||||
LOG.warning("Trim memory: running critically low");
|
LOG.warning("Trim memory: running critically low");
|
||||||
// If we're not in the foreground, clear the UI to save memory
|
// If we're not in the foreground, clear the UI to save memory
|
||||||
if (app.isRunningInBackground()) hideUi();
|
if (app.isRunningInBackground()) hideUi();
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log handler that raises all records at or above a given source level to a
|
||||||
|
* given destination level. This affects the level seen by subsequent handlers.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class LevelRaisingHandler extends Handler {
|
||||||
|
|
||||||
|
private final Level dest;
|
||||||
|
private final int srcInt, destInt;
|
||||||
|
|
||||||
|
LevelRaisingHandler(Level src, Level dest) {
|
||||||
|
this.dest = dest;
|
||||||
|
srcInt = src.intValue();
|
||||||
|
destInt = dest.intValue();
|
||||||
|
if (srcInt > destInt) throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void publish(LogRecord record) {
|
||||||
|
int recordInt = record.getLevel().intValue();
|
||||||
|
if (recordInt >= srcInt && recordInt < destInt) record.setLevel(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -150,7 +150,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
|
|||||||
// Get permissions
|
// Get permissions
|
||||||
String[] requestedPermissions = packageInfo.requestedPermissions;
|
String[] requestedPermissions = packageInfo.requestedPermissions;
|
||||||
if (requestedPermissions == null) return false;
|
if (requestedPermissions == null) return false;
|
||||||
if (SDK_INT >= 16 && SDK_INT < 23) {
|
if (SDK_INT < 23) {
|
||||||
// Check whether the permission has been requested and granted
|
// Check whether the permission has been requested and granted
|
||||||
int[] flags = packageInfo.requestedPermissionsFlags;
|
int[] flags = packageInfo.requestedPermissionsFlags;
|
||||||
for (int i = 0; i < requestedPermissions.length; i++) {
|
for (int i = 0; i < requestedPermissions.length; i++) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.account;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.TextInputEditText;
|
import android.support.design.widget.TextInputEditText;
|
||||||
import android.support.design.widget.TextInputLayout;
|
import android.support.design.widget.TextInputLayout;
|
||||||
|
import android.text.Editable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -10,18 +11,15 @@ import android.widget.Button;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
|
|
||||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.setError;
|
import static org.briarproject.briar.android.util.UiUtils.setError;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -64,12 +62,6 @@ public class AuthorNameFragment extends SetupFragment {
|
|||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
showSoftKeyboard(authorNameInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getHelpText() {
|
protected String getHelpText() {
|
||||||
return getString(R.string.setup_name_explanation);
|
return getString(R.string.setup_name_explanation);
|
||||||
@@ -77,20 +69,21 @@ public class AuthorNameFragment extends SetupFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
|
public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
|
||||||
int authorNameLength = StringUtils.toUtf8(authorName.toString()).length;
|
int authorNameLength = toUtf8(authorName.toString().trim()).length;
|
||||||
boolean error = authorNameLength > MAX_AUTHOR_NAME_LENGTH;
|
boolean error = authorNameLength > MAX_AUTHOR_NAME_LENGTH;
|
||||||
setError(authorNameWrapper, getString(R.string.name_too_long), error);
|
setError(authorNameWrapper, getString(R.string.name_too_long), error);
|
||||||
boolean enabled = authorNameLength > 0 && !error;
|
boolean enabled = authorNameLength > 0 && !error;
|
||||||
authorNameInput
|
|
||||||
.setImeOptions(enabled ? IME_ACTION_NEXT : IME_ACTION_NONE);
|
|
||||||
authorNameInput.setOnEditorActionListener(enabled ? this : null);
|
authorNameInput.setOnEditorActionListener(enabled ? this : null);
|
||||||
nextButton.setEnabled(enabled);
|
nextButton.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
setupController.setAuthorName(authorNameInput.getText().toString());
|
Editable text = authorNameInput.getText();
|
||||||
setupController.showPasswordFragment();
|
if (text != null) {
|
||||||
|
setupController.setAuthorName(text.toString().trim());
|
||||||
|
setupController.showPasswordFragment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ public class SetPasswordFragment extends SetupFragment {
|
|||||||
strengthMeter = v.findViewById(R.id.strength_meter);
|
strengthMeter = v.findViewById(R.id.strength_meter);
|
||||||
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
|
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
|
||||||
passwordEntry = v.findViewById(R.id.password_entry);
|
passwordEntry = v.findViewById(R.id.password_entry);
|
||||||
passwordEntry.requestFocus();
|
|
||||||
passwordConfirmationWrapper =
|
passwordConfirmationWrapper =
|
||||||
v.findViewById(R.id.password_confirm_wrapper);
|
v.findViewById(R.id.password_confirm_wrapper);
|
||||||
passwordConfirmation = v.findViewById(R.id.password_confirm);
|
passwordConfirmation = v.findViewById(R.id.password_confirm);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.activity;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
@@ -12,7 +11,6 @@ import android.support.v7.widget.Toolbar;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
@@ -44,10 +42,10 @@ import javax.inject.Inject;
|
|||||||
import static android.arch.lifecycle.Lifecycle.State.STARTED;
|
import static android.arch.lifecycle.Lifecycle.State.STARTED;
|
||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
|
||||||
import static java.util.logging.Level.INFO;
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Warning: Some activities don't extend {@link BaseActivity}.
|
* Warning: Some activities don't extend {@link BaseActivity}.
|
||||||
@@ -217,17 +215,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showSoftKeyboard(View view) {
|
|
||||||
Object o = getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
((InputMethodManager) o).showSoftInput(view, SHOW_IMPLICIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideSoftKeyboard(View view) {
|
|
||||||
IBinder token = view.getWindowToken();
|
|
||||||
Object o = getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public void handleDbException(DbException e) {
|
public void handleDbException(DbException e) {
|
||||||
supportFinishAfterTransition();
|
supportFinishAfterTransition();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.briarproject.briar.android.attachment;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory.Options;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
@@ -12,11 +14,17 @@ import org.briarproject.briar.api.messaging.AttachmentHeader;
|
|||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.jsoup.UnsupportedMimeTypeException;
|
import org.jsoup.UnsupportedMimeTypeException;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.graphics.Bitmap.CompressFormat.JPEG;
|
||||||
|
import static android.graphics.BitmapFactory.decodeStream;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
@@ -24,6 +32,7 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AttachmentCreationTask {
|
class AttachmentCreationTask {
|
||||||
@@ -31,8 +40,11 @@ class AttachmentCreationTask {
|
|||||||
private static Logger LOG =
|
private static Logger LOG =
|
||||||
getLogger(AttachmentCreationTask.class.getName());
|
getLogger(AttachmentCreationTask.class.getName());
|
||||||
|
|
||||||
|
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
|
||||||
|
|
||||||
private final MessagingManager messagingManager;
|
private final MessagingManager messagingManager;
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
|
private final ImageSizeCalculator imageSizeCalculator;
|
||||||
private final GroupId groupId;
|
private final GroupId groupId;
|
||||||
private final Collection<Uri> uris;
|
private final Collection<Uri> uris;
|
||||||
private final boolean needsSize;
|
private final boolean needsSize;
|
||||||
@@ -43,24 +55,26 @@ class AttachmentCreationTask {
|
|||||||
|
|
||||||
AttachmentCreationTask(MessagingManager messagingManager,
|
AttachmentCreationTask(MessagingManager messagingManager,
|
||||||
ContentResolver contentResolver,
|
ContentResolver contentResolver,
|
||||||
AttachmentCreator attachmentCreator, GroupId groupId,
|
AttachmentCreator attachmentCreator,
|
||||||
Collection<Uri> uris, boolean needsSize) {
|
ImageSizeCalculator imageSizeCalculator,
|
||||||
|
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
this.contentResolver = contentResolver;
|
this.contentResolver = contentResolver;
|
||||||
|
this.imageSizeCalculator = imageSizeCalculator;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.uris = uris;
|
this.uris = uris;
|
||||||
this.needsSize = needsSize;
|
this.needsSize = needsSize;
|
||||||
this.attachmentCreator = attachmentCreator;
|
this.attachmentCreator = attachmentCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
void cancel() {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
attachmentCreator = null;
|
attachmentCreator = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
public void storeAttachments() {
|
void storeAttachments() {
|
||||||
for (Uri uri: uris) processUri(uri);
|
for (Uri uri : uris) processUri(uri);
|
||||||
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
||||||
if (!canceled && attachmentCreator != null)
|
if (!canceled && attachmentCreator != null)
|
||||||
attachmentCreator.onAttachmentCreationFinished();
|
attachmentCreator.onAttachmentCreationFinished();
|
||||||
@@ -98,6 +112,8 @@ class AttachmentCreationTask {
|
|||||||
}
|
}
|
||||||
InputStream is = contentResolver.openInputStream(uri);
|
InputStream is = contentResolver.openInputStream(uri);
|
||||||
if (is == null) throw new IOException();
|
if (is == null) throw new IOException();
|
||||||
|
is = compressImage(is, contentType);
|
||||||
|
contentType = "image/jpeg";
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
AttachmentHeader h = messagingManager
|
AttachmentHeader h = messagingManager
|
||||||
.addLocalAttachment(groupId, timestamp, contentType, is);
|
.addLocalAttachment(groupId, timestamp, contentType, is);
|
||||||
@@ -113,4 +129,48 @@ class AttachmentCreationTask {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InputStream compressImage(InputStream is, String contentType)
|
||||||
|
throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = createBitmap(is, contentType);
|
||||||
|
for (int quality = 100; quality >= 0; quality -= 10) {
|
||||||
|
if (!bitmap.compress(JPEG, quality, out))
|
||||||
|
throw new IOException();
|
||||||
|
if (out.size() <= MAX_IMAGE_SIZE) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Compressed image to "
|
||||||
|
+ out.size() + " bytes, quality " + quality);
|
||||||
|
}
|
||||||
|
return new ByteArrayInputStream(out.toByteArray());
|
||||||
|
}
|
||||||
|
out.reset();
|
||||||
|
}
|
||||||
|
throw new IOException();
|
||||||
|
} finally {
|
||||||
|
tryToClose(is, LOG, WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap createBitmap(InputStream is, String contentType)
|
||||||
|
throws IOException {
|
||||||
|
is = new BufferedInputStream(is);
|
||||||
|
Size size = imageSizeCalculator.getSize(is, contentType);
|
||||||
|
if (size.error) throw new IOException();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Original image size: " + size.width + "x" + size.height);
|
||||||
|
int dimension = Math.max(size.width, size.height);
|
||||||
|
int inSampleSize = 1;
|
||||||
|
while (dimension > MAX_ATTACHMENT_DIMENSION) {
|
||||||
|
inSampleSize *= 2;
|
||||||
|
dimension /= 2;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Scaling attachment by factor of " + inSampleSize);
|
||||||
|
Options options = new Options();
|
||||||
|
options.inSampleSize = inSampleSize;
|
||||||
|
Bitmap bitmap = decodeStream(is, null, options);
|
||||||
|
if (bitmap == null) throw new IOException();
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +1,24 @@
|
|||||||
package org.briarproject.briar.android.attachment;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.arch.lifecycle.LiveData;
|
import android.arch.lifecycle.LiveData;
|
||||||
import android.arch.lifecycle.MutableLiveData;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.FileTooBigException;
|
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
|
||||||
import org.jsoup.UnsupportedMimeTypeException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentCreator {
|
public interface AttachmentCreator {
|
||||||
|
|
||||||
private static Logger LOG = getLogger(AttachmentCreator.class.getName());
|
|
||||||
|
|
||||||
private final Application app;
|
|
||||||
@IoExecutor
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final MessagingManager messagingManager;
|
|
||||||
private final AttachmentRetriever retriever;
|
|
||||||
|
|
||||||
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
|
|
||||||
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
|
|
||||||
new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
private final MutableLiveData<AttachmentResult> result =
|
|
||||||
new MutableLiveData<>();
|
|
||||||
@Nullable
|
|
||||||
private AttachmentCreationTask task;
|
|
||||||
|
|
||||||
public AttachmentCreator(Application app, @IoExecutor Executor ioExecutor,
|
|
||||||
MessagingManager messagingManager, AttachmentRetriever retriever) {
|
|
||||||
this.app = app;
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.messagingManager = messagingManager;
|
|
||||||
this.retriever = retriever;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public LiveData<AttachmentResult> storeAttachments(
|
LiveData<AttachmentResult> storeAttachments(LiveData<GroupId> groupId,
|
||||||
LiveData<GroupId> groupId, Collection<Uri> newUris) {
|
Collection<Uri> newUris);
|
||||||
if (task != null || !uris.isEmpty())
|
|
||||||
throw new IllegalStateException();
|
|
||||||
uris.addAll(newUris);
|
|
||||||
observeForeverOnce(groupId, id -> {
|
|
||||||
if (id == null) throw new IllegalStateException();
|
|
||||||
boolean needsSize = uris.size() == 1;
|
|
||||||
task = new AttachmentCreationTask(messagingManager,
|
|
||||||
app.getContentResolver(), this, id, uris, needsSize);
|
|
||||||
ioExecutor.execute(() -> task.storeAttachments());
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should be only called after configuration changes.
|
* This should be only called after configuration changes.
|
||||||
@@ -84,68 +26,10 @@ public class AttachmentCreator {
|
|||||||
* They are already being created and returned by the existing LiveData.
|
* They are already being created and returned by the existing LiveData.
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public LiveData<AttachmentResult> getLiveAttachments() {
|
LiveData<AttachmentResult> getLiveAttachments();
|
||||||
if (task == null || uris.isEmpty())
|
|
||||||
throw new IllegalStateException();
|
|
||||||
// A task is already running. It will update the result LiveData.
|
|
||||||
// So nothing more to do here.
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
|
||||||
boolean needsSize) {
|
|
||||||
// get and cache AttachmentItem for ImagePreview
|
|
||||||
try {
|
|
||||||
Attachment a = retriever.getMessageAttachment(h);
|
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, needsSize);
|
|
||||||
if (item.hasError()) throw new IOException();
|
|
||||||
AttachmentItemResult itemResult =
|
|
||||||
new AttachmentItemResult(uri, item);
|
|
||||||
itemResults.add(itemResult);
|
|
||||||
result.postValue(getResult(false));
|
|
||||||
} catch (IOException | DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
onAttachmentError(uri, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void onAttachmentError(Uri uri, Throwable t) {
|
|
||||||
// get error message
|
|
||||||
String errorMsg;
|
|
||||||
if (t instanceof UnsupportedMimeTypeException) {
|
|
||||||
String mimeType = ((UnsupportedMimeTypeException) t).getMimeType();
|
|
||||||
errorMsg = app.getString(
|
|
||||||
R.string.image_attach_error_invalid_mime_type, mimeType);
|
|
||||||
} else if (t instanceof FileTooBigException) {
|
|
||||||
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
|
|
||||||
errorMsg = app.getString(R.string.image_attach_error_too_big, mb);
|
|
||||||
} else {
|
|
||||||
errorMsg = null; // generic error
|
|
||||||
}
|
|
||||||
AttachmentItemResult itemResult =
|
|
||||||
new AttachmentItemResult(uri, errorMsg);
|
|
||||||
itemResults.add(itemResult);
|
|
||||||
result.postValue(getResult(false));
|
|
||||||
// expect to receive a cancel from the UI
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void onAttachmentCreationFinished() {
|
|
||||||
result.postValue(getResult(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
List<AttachmentHeader> getAttachmentHeadersForSending();
|
||||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
|
||||||
for (AttachmentItemResult itemResult : itemResults) {
|
|
||||||
// check if we are trying to send attachment items with errors
|
|
||||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
|
||||||
headers.add(itemResult.getItem().getHeader());
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the attachments as sent and adds the items to the cache for display
|
* Marks the attachments as sent and adds the items to the cache for display
|
||||||
@@ -153,66 +37,24 @@ public class AttachmentCreator {
|
|||||||
* @param id The MessageId of the sent message.
|
* @param id The MessageId of the sent message.
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public void onAttachmentsSent(MessageId id) {
|
void onAttachmentsSent(MessageId id);
|
||||||
List<AttachmentItem> items = new ArrayList<>(itemResults.size());
|
|
||||||
for (AttachmentItemResult itemResult : itemResults) {
|
|
||||||
// check if we are trying to send attachment items with errors
|
|
||||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
|
||||||
items.add(itemResult.getItem());
|
|
||||||
}
|
|
||||||
retriever.cachePut(id, items);
|
|
||||||
resetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Needs to be called when created attachments will not be sent anymore.
|
* Needs to be called when created attachments will not be sent anymore.
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public void cancel() {
|
void cancel();
|
||||||
if (task == null) throw new AssertionError();
|
|
||||||
task.cancel();
|
|
||||||
deleteUnsentAttachments();
|
|
||||||
resetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void resetState() {
|
void deleteUnsentAttachments();
|
||||||
task = null;
|
|
||||||
uris.clear();
|
|
||||||
itemResults.clear();
|
|
||||||
result.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@IoExecutor
|
||||||
public void deleteUnsentAttachments() {
|
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
||||||
// Make a copy for the IoExecutor as we clear the itemResults soon
|
boolean needsSize);
|
||||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
|
||||||
for (AttachmentItemResult itemResult : itemResults) {
|
|
||||||
// check if we are trying to send attachment items with errors
|
|
||||||
if (itemResult.getItem() != null)
|
|
||||||
headers.add(itemResult.getItem().getHeader());
|
|
||||||
}
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
for (AttachmentHeader header : headers) {
|
|
||||||
try {
|
|
||||||
messagingManager.removeAttachment(header);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private AttachmentResult getResult(boolean finished) {
|
@IoExecutor
|
||||||
// Make a copy of the list,
|
void onAttachmentError(Uri uri, Throwable t);
|
||||||
// because our copy will continue to change in the background.
|
|
||||||
// (As it's a CopyOnWriteArrayList,
|
|
||||||
// the code that receives the result can safely do simple things
|
|
||||||
// like iterating over the list,
|
|
||||||
// but anything that involves calling more than one list method
|
|
||||||
// is still unsafe.)
|
|
||||||
Collection<AttachmentItemResult> items = new ArrayList<>(itemResults);
|
|
||||||
return new AttachmentResult(items, finished);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
@IoExecutor
|
||||||
|
void onAttachmentCreationFinished();
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.FileTooBigException;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
import org.jsoup.UnsupportedMimeTypeException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
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.util.UiUtils.observeForeverOnce;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class AttachmentCreatorImpl implements AttachmentCreator {
|
||||||
|
|
||||||
|
private static Logger LOG =
|
||||||
|
getLogger(AttachmentCreatorImpl.class.getName());
|
||||||
|
|
||||||
|
private final Application app;
|
||||||
|
@IoExecutor
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
private final AttachmentRetriever retriever;
|
||||||
|
private final ImageSizeCalculator imageSizeCalculator;
|
||||||
|
|
||||||
|
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
|
||||||
|
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
|
||||||
|
new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private AttachmentCreationTask task;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private volatile MutableLiveData<AttachmentResult> result;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
|
||||||
|
MessagingManager messagingManager, AttachmentRetriever retriever,
|
||||||
|
ImageSizeCalculator imageSizeCalculator) {
|
||||||
|
this.app = app;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.retriever = retriever;
|
||||||
|
this.imageSizeCalculator = imageSizeCalculator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public LiveData<AttachmentResult> storeAttachments(
|
||||||
|
LiveData<GroupId> groupId, Collection<Uri> newUris) {
|
||||||
|
if (task != null || result != null || !uris.isEmpty())
|
||||||
|
throw new IllegalStateException();
|
||||||
|
MutableLiveData<AttachmentResult> result = new MutableLiveData<>();
|
||||||
|
this.result = result;
|
||||||
|
uris.addAll(newUris);
|
||||||
|
observeForeverOnce(groupId, id -> {
|
||||||
|
if (id == null) throw new IllegalStateException();
|
||||||
|
boolean needsSize = uris.size() == 1;
|
||||||
|
task = new AttachmentCreationTask(messagingManager,
|
||||||
|
app.getContentResolver(), this, imageSizeCalculator, id,
|
||||||
|
uris, needsSize);
|
||||||
|
ioExecutor.execute(() -> task.storeAttachments());
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public LiveData<AttachmentResult> getLiveAttachments() {
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (task == null || result == null || uris.isEmpty())
|
||||||
|
throw new IllegalStateException();
|
||||||
|
// A task is already running. It will update the result LiveData.
|
||||||
|
// So nothing more to do here.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@IoExecutor
|
||||||
|
public void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
||||||
|
boolean needsSize) {
|
||||||
|
// get and cache AttachmentItem for ImagePreview
|
||||||
|
try {
|
||||||
|
Attachment a = retriever.getMessageAttachment(h);
|
||||||
|
AttachmentItem item = retriever.getAttachmentItem(a, needsSize);
|
||||||
|
if (item.hasError()) throw new IOException();
|
||||||
|
AttachmentItemResult itemResult =
|
||||||
|
new AttachmentItemResult(uri, item);
|
||||||
|
itemResults.add(itemResult);
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (result != null) result.postValue(getResult(false));
|
||||||
|
} catch (IOException | DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
onAttachmentError(uri, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@IoExecutor
|
||||||
|
public void onAttachmentError(Uri uri, Throwable t) {
|
||||||
|
// get error message
|
||||||
|
String errorMsg;
|
||||||
|
if (t instanceof UnsupportedMimeTypeException) {
|
||||||
|
String mimeType = ((UnsupportedMimeTypeException) t).getMimeType();
|
||||||
|
errorMsg = app.getString(
|
||||||
|
R.string.image_attach_error_invalid_mime_type, mimeType);
|
||||||
|
} else if (t instanceof FileTooBigException) {
|
||||||
|
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
|
||||||
|
errorMsg = app.getString(R.string.image_attach_error_too_big, mb);
|
||||||
|
} else {
|
||||||
|
errorMsg = null; // generic error
|
||||||
|
}
|
||||||
|
AttachmentItemResult itemResult =
|
||||||
|
new AttachmentItemResult(uri, errorMsg);
|
||||||
|
itemResults.add(itemResult);
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (result != null) result.postValue(getResult(false));
|
||||||
|
// expect to receive a cancel from the UI
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@IoExecutor
|
||||||
|
public void onAttachmentCreationFinished() {
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (result != null) result.postValue(getResult(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
||||||
|
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||||
|
for (AttachmentItemResult itemResult : itemResults) {
|
||||||
|
// check if we are trying to send attachment items with errors
|
||||||
|
if (itemResult.getItem() == null) throw new IllegalStateException();
|
||||||
|
headers.add(itemResult.getItem().getHeader());
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void onAttachmentsSent(MessageId id) {
|
||||||
|
List<AttachmentItem> items = new ArrayList<>(itemResults.size());
|
||||||
|
for (AttachmentItemResult itemResult : itemResults) {
|
||||||
|
// check if we are trying to send attachment items with errors
|
||||||
|
if (itemResult.getItem() == null) throw new IllegalStateException();
|
||||||
|
items.add(itemResult.getItem());
|
||||||
|
}
|
||||||
|
retriever.cachePut(id, items);
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void cancel() {
|
||||||
|
if (task == null) throw new AssertionError();
|
||||||
|
task.cancel();
|
||||||
|
deleteUnsentAttachments();
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void resetState() {
|
||||||
|
task = null;
|
||||||
|
uris.clear();
|
||||||
|
itemResults.clear();
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (result != null) {
|
||||||
|
result.setValue(null);
|
||||||
|
this.result = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void deleteUnsentAttachments() {
|
||||||
|
// Make a copy for the IoExecutor as we clear the itemResults soon
|
||||||
|
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||||
|
for (AttachmentItemResult itemResult : itemResults) {
|
||||||
|
// check if we are trying to send attachment items with errors
|
||||||
|
if (itemResult.getItem() != null)
|
||||||
|
headers.add(itemResult.getItem().getHeader());
|
||||||
|
}
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
for (AttachmentHeader header : headers) {
|
||||||
|
try {
|
||||||
|
messagingManager.removeAttachment(header);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttachmentResult getResult(boolean finished) {
|
||||||
|
// Make a copy of the list,
|
||||||
|
// because our copy will continue to change in the background.
|
||||||
|
// (As it's a CopyOnWriteArrayList,
|
||||||
|
// the code that receives the result can safely do simple things
|
||||||
|
// like iterating over the list,
|
||||||
|
// but anything that involves calling more than one list method
|
||||||
|
// is still unsafe.)
|
||||||
|
Collection<AttachmentItemResult> items = new ArrayList<>(itemResults);
|
||||||
|
return new AttachmentResult(items, finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentDimensions {
|
class AttachmentDimensions {
|
||||||
|
|
||||||
final int defaultSize;
|
final int defaultSize;
|
||||||
final int minWidth, maxWidth;
|
final int minWidth, maxWidth;
|
||||||
@@ -26,7 +26,7 @@ public class AttachmentDimensions {
|
|||||||
this.maxHeight = maxHeight;
|
this.maxHeight = maxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||||
int defaultSize =
|
int defaultSize =
|
||||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||||
int minWidth = res.getDimensionPixelSize(
|
int minWidth = res.getDimensionPixelSize(
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class AttachmentItem implements Parcelable {
|
|||||||
header = new AttachmentHeader(messageId, mimeType);
|
header = new AttachmentHeader(messageId, mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentHeader getHeader() {
|
public AttachmentHeader getHeader() {
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import android.arch.lifecycle.LiveData;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
|
@NotNullByDefault
|
||||||
public interface AttachmentManager {
|
public interface AttachmentManager {
|
||||||
|
|
||||||
LiveData<AttachmentResult> storeAttachments(Collection<Uri> uri,
|
LiveData<AttachmentResult> storeAttachments(Collection<Uri> uri,
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class AttachmentModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
|
||||||
|
return imageHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
|
||||||
|
return new ImageSizeCalculator(imageHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
AttachmentDimensions provideAttachmentDimensions(Application app) {
|
||||||
|
return getAttachmentDimensions(app.getResources());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
AttachmentRetriever provideAttachmentRetriever(
|
||||||
|
AttachmentRetrieverImpl attachmentRetriever) {
|
||||||
|
return attachmentRetriever;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
AttachmentCreator provideAttachmentCreator(
|
||||||
|
AttachmentCreatorImpl attachmentCreator) {
|
||||||
|
return attachmentCreator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,276 +1,29 @@
|
|||||||
package org.briarproject.briar.android.attachment;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.BitmapFactory.Options;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.support.media.ExifInterface;
|
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
|
|
||||||
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Pair;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
|
|
||||||
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
|
|
||||||
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
|
|
||||||
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
|
|
||||||
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
|
|
||||||
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
|
|
||||||
import static android.support.media.ExifInterface.TAG_ORIENTATION;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentRetriever {
|
public interface AttachmentRetriever {
|
||||||
|
|
||||||
private static final Logger LOG =
|
void cachePut(MessageId messageId, List<AttachmentItem> attachments);
|
||||||
getLogger(AttachmentRetriever.class.getName());
|
|
||||||
private static final int READ_LIMIT = 1024 * 8192;
|
|
||||||
|
|
||||||
private final MessagingManager messagingManager;
|
|
||||||
private final ImageHelper imageHelper;
|
|
||||||
private final int defaultSize;
|
|
||||||
private final int minWidth, maxWidth;
|
|
||||||
private final int minHeight, maxHeight;
|
|
||||||
|
|
||||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
|
||||||
new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
AttachmentRetriever(MessagingManager messagingManager,
|
|
||||||
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
|
||||||
this.messagingManager = messagingManager;
|
|
||||||
this.imageHelper = imageHelper;
|
|
||||||
defaultSize = dimensions.defaultSize;
|
|
||||||
minWidth = dimensions.minWidth;
|
|
||||||
maxWidth = dimensions.maxWidth;
|
|
||||||
minHeight = dimensions.minHeight;
|
|
||||||
maxHeight = dimensions.maxHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AttachmentRetriever(MessagingManager messagingManager,
|
|
||||||
AttachmentDimensions dimensions) {
|
|
||||||
this(messagingManager, dimensions, new ImageHelper() {
|
|
||||||
@Override
|
|
||||||
public DecodeResult decodeStream(InputStream is) {
|
|
||||||
Options options = new Options();
|
|
||||||
options.inJustDecodeBounds = true;
|
|
||||||
BitmapFactory.decodeStream(is, null, options);
|
|
||||||
String mimeType = options.outMimeType;
|
|
||||||
if (mimeType == null) mimeType = "";
|
|
||||||
return new DecodeResult(options.outWidth, options.outHeight,
|
|
||||||
mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getExtensionFromMimeType(String mimeType) {
|
|
||||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
|
||||||
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cachePut(MessageId messageId, List<AttachmentItem> attachments) {
|
|
||||||
attachmentCache.put(messageId, attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
List<AttachmentItem> cacheGet(MessageId messageId);
|
||||||
return attachmentCache.get(messageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
Attachment getMessageAttachment(AttachmentHeader h) throws DbException;
|
||||||
public List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
|
||||||
List<AttachmentHeader> headers) throws DbException {
|
|
||||||
long start = now();
|
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
|
||||||
new ArrayList<>(headers.size());
|
|
||||||
for (AttachmentHeader h : headers) {
|
|
||||||
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
|
||||||
attachments.add(new Pair<>(h, a));
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Loading attachments", start);
|
|
||||||
return attachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
|
||||||
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
|
|
||||||
return messagingManager.getAttachment(h.getMessageId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
|
||||||
* <p>
|
|
||||||
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
|
||||||
*/
|
|
||||||
public List<AttachmentItem> getAttachmentItems(
|
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
|
||||||
boolean needsSize = attachments.size() == 1;
|
|
||||||
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
|
||||||
for (Pair<AttachmentHeader, Attachment> a : attachments) {
|
|
||||||
AttachmentItem item =
|
|
||||||
getAttachmentItem(a.getFirst(), a.getSecond(), needsSize);
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
||||||
* {@link InputStream} which will be closed when this method returns.
|
* {@link InputStream} which will be closed when this method returns.
|
||||||
*/
|
*/
|
||||||
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
AttachmentItem getAttachmentItem(Attachment a, boolean needsSize);
|
||||||
boolean needsSize) {
|
|
||||||
if (!needsSize) {
|
|
||||||
String extension =
|
|
||||||
imageHelper.getExtensionFromMimeType(h.getContentType());
|
|
||||||
boolean hasError = false;
|
|
||||||
if (extension == null) {
|
|
||||||
extension = "";
|
|
||||||
hasError = true;
|
|
||||||
}
|
|
||||||
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Size size = new Size();
|
|
||||||
InputStream is = new MarkEnforcingInputStream(
|
|
||||||
new BufferedInputStream(a.getStream()));
|
|
||||||
is.mark(READ_LIMIT);
|
|
||||||
try {
|
|
||||||
// use exif to get size
|
|
||||||
if (h.getContentType().equals("image/jpeg")) {
|
|
||||||
size = getSizeFromExif(is);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// use BitmapFactory to get size
|
|
||||||
if (size.error) {
|
|
||||||
is.reset();
|
|
||||||
// need to mark again to re-add read limit
|
|
||||||
is.mark(READ_LIMIT);
|
|
||||||
size = getSizeFromBitmap(is);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
} finally {
|
|
||||||
tryToClose(is, LOG, WARNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate thumbnail size
|
|
||||||
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
|
||||||
if (!size.error) {
|
|
||||||
thumbnailSize =
|
|
||||||
getThumbnailSize(size.width, size.height, size.mimeType);
|
|
||||||
}
|
|
||||||
// get file extension
|
|
||||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
|
||||||
boolean hasError = extension == null || size.error;
|
|
||||||
if (!h.getContentType().equals(size.mimeType)) {
|
|
||||||
if (LOG.isLoggable(WARNING)) {
|
|
||||||
LOG.warning("Header has different mime type (" +
|
|
||||||
h.getContentType() + ") than image (" + size.mimeType +
|
|
||||||
").");
|
|
||||||
}
|
|
||||||
hasError = true;
|
|
||||||
}
|
|
||||||
if (extension == null) extension = "";
|
|
||||||
return new AttachmentItem(h, size.width, size.height, extension,
|
|
||||||
thumbnailSize.width, thumbnailSize.height, hasError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
|
||||||
*/
|
|
||||||
private Size getSizeFromExif(InputStream is) throws IOException {
|
|
||||||
ExifInterface exif = new ExifInterface(is);
|
|
||||||
// these can return 0 independent of default value
|
|
||||||
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
|
|
||||||
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
|
|
||||||
if (width == 0 || height == 0) return new Size();
|
|
||||||
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
|
|
||||||
if (orientation == ORIENTATION_ROTATE_90 ||
|
|
||||||
orientation == ORIENTATION_ROTATE_270 ||
|
|
||||||
orientation == ORIENTATION_TRANSVERSE ||
|
|
||||||
orientation == ORIENTATION_TRANSPOSE) {
|
|
||||||
//noinspection SuspiciousNameCombination
|
|
||||||
return new Size(height, width, "image/jpeg");
|
|
||||||
}
|
|
||||||
return new Size(width, height, "image/jpeg");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the size of any image {@link InputStream}.
|
|
||||||
*/
|
|
||||||
private Size getSizeFromBitmap(InputStream is) {
|
|
||||||
DecodeResult result = imageHelper.decodeStream(is);
|
|
||||||
if (result.width < 1 || result.height < 1) return new Size();
|
|
||||||
return new Size(result.width, result.height, result.mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Size getThumbnailSize(int width, int height, String mimeType) {
|
|
||||||
float widthPercentage = maxWidth / (float) width;
|
|
||||||
float heightPercentage = maxHeight / (float) height;
|
|
||||||
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
|
||||||
if (scaleFactor > 1) scaleFactor = 1f;
|
|
||||||
int thumbnailWidth = (int) (width * scaleFactor);
|
|
||||||
int thumbnailHeight = (int) (height * scaleFactor);
|
|
||||||
if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) {
|
|
||||||
widthPercentage = minWidth / (float) width;
|
|
||||||
heightPercentage = minHeight / (float) height;
|
|
||||||
scaleFactor = Math.max(widthPercentage, heightPercentage);
|
|
||||||
thumbnailWidth = (int) (width * scaleFactor);
|
|
||||||
thumbnailHeight = (int) (height * scaleFactor);
|
|
||||||
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
|
||||||
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
|
||||||
}
|
|
||||||
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Size {
|
|
||||||
|
|
||||||
private final int width;
|
|
||||||
private final int height;
|
|
||||||
private final String mimeType;
|
|
||||||
private final boolean error;
|
|
||||||
|
|
||||||
private Size(int width, int height, String mimeType) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.mimeType = mimeType;
|
|
||||||
this.error = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Size() {
|
|
||||||
this.width = 0;
|
|
||||||
this.height = 0;
|
|
||||||
this.mimeType = "";
|
|
||||||
this.error = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(AttachmentRetrieverImpl.class.getName());
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
private final ImageHelper imageHelper;
|
||||||
|
private final ImageSizeCalculator imageSizeCalculator;
|
||||||
|
private final int defaultSize;
|
||||||
|
private final int minWidth, maxWidth;
|
||||||
|
private final int minHeight, maxHeight;
|
||||||
|
|
||||||
|
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AttachmentRetrieverImpl(MessagingManager messagingManager,
|
||||||
|
AttachmentDimensions dimensions, ImageHelper imageHelper,
|
||||||
|
ImageSizeCalculator imageSizeCalculator) {
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.imageHelper = imageHelper;
|
||||||
|
this.imageSizeCalculator = imageSizeCalculator;
|
||||||
|
defaultSize = dimensions.defaultSize;
|
||||||
|
minWidth = dimensions.minWidth;
|
||||||
|
maxWidth = dimensions.maxWidth;
|
||||||
|
minHeight = dimensions.minHeight;
|
||||||
|
maxHeight = dimensions.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cachePut(MessageId messageId,
|
||||||
|
List<AttachmentItem> attachments) {
|
||||||
|
attachmentCache.put(messageId, attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
||||||
|
return attachmentCache.get(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Attachment getMessageAttachment(AttachmentHeader h)
|
||||||
|
throws DbException {
|
||||||
|
return messagingManager.getAttachment(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) {
|
||||||
|
AttachmentHeader h = a.getHeader();
|
||||||
|
if (!needsSize) {
|
||||||
|
String extension =
|
||||||
|
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||||
|
boolean hasError = false;
|
||||||
|
if (extension == null) {
|
||||||
|
extension = "";
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream is = new BufferedInputStream(a.getStream());
|
||||||
|
Size size = imageSizeCalculator.getSize(is, h.getContentType());
|
||||||
|
|
||||||
|
// calculate thumbnail size
|
||||||
|
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
||||||
|
if (!size.error) {
|
||||||
|
thumbnailSize =
|
||||||
|
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||||
|
}
|
||||||
|
// get file extension
|
||||||
|
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||||
|
boolean hasError = extension == null || size.error;
|
||||||
|
if (!h.getContentType().equals(size.mimeType)) {
|
||||||
|
if (LOG.isLoggable(WARNING)) {
|
||||||
|
LOG.warning("Header has different mime type (" +
|
||||||
|
h.getContentType() + ") than image (" + size.mimeType +
|
||||||
|
").");
|
||||||
|
}
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
if (extension == null) extension = "";
|
||||||
|
return new AttachmentItem(h, size.width, size.height, extension,
|
||||||
|
thumbnailSize.width, thumbnailSize.height, hasError);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size getThumbnailSize(int width, int height, String mimeType) {
|
||||||
|
float widthPercentage = maxWidth / (float) width;
|
||||||
|
float heightPercentage = maxHeight / (float) height;
|
||||||
|
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||||
|
if (scaleFactor > 1) scaleFactor = 1f;
|
||||||
|
int thumbnailWidth = (int) (width * scaleFactor);
|
||||||
|
int thumbnailHeight = (int) (height * scaleFactor);
|
||||||
|
if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) {
|
||||||
|
widthPercentage = minWidth / (float) width;
|
||||||
|
heightPercentage = minHeight / (float) height;
|
||||||
|
scaleFactor = Math.max(widthPercentage, heightPercentage);
|
||||||
|
thumbnailWidth = (int) (width * scaleFactor);
|
||||||
|
thumbnailHeight = (int) (height * scaleFactor);
|
||||||
|
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
||||||
|
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
||||||
|
}
|
||||||
|
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface ImageHelper {
|
public interface ImageHelper {
|
||||||
|
|
||||||
DecodeResult decodeStream(InputStream is);
|
DecodeResult decodeStream(InputStream is);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImageHelperImpl implements ImageHelper {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ImageHelperImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DecodeResult decodeStream(InputStream is) {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeStream(is, null, options);
|
||||||
|
String mimeType = options.outMimeType;
|
||||||
|
if (mimeType == null) mimeType = "";
|
||||||
|
return new DecodeResult(options.outWidth, options.outHeight,
|
||||||
|
mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getExtensionFromMimeType(String mimeType) {
|
||||||
|
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
|
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.support.media.ExifInterface;
|
||||||
|
|
||||||
|
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
|
||||||
|
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
|
||||||
|
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
|
||||||
|
import static android.support.media.ExifInterface.TAG_ORIENTATION;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImageSizeCalculator {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ImageSizeCalculator.class.getName());
|
||||||
|
|
||||||
|
private static final int READ_LIMIT = 1024 * 8192;
|
||||||
|
|
||||||
|
private final ImageHelper imageHelper;
|
||||||
|
|
||||||
|
ImageSizeCalculator(ImageHelper imageHelper) {
|
||||||
|
this.imageHelper = imageHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size getSize(InputStream is, String contentType) {
|
||||||
|
Size size = new Size();
|
||||||
|
is = new MarkEnforcingInputStream(is);
|
||||||
|
is.mark(READ_LIMIT);
|
||||||
|
if (contentType.equals("image/jpeg")) {
|
||||||
|
try {
|
||||||
|
// use exif to get size
|
||||||
|
size = getSizeFromExif(is);
|
||||||
|
is.reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (size.error) {
|
||||||
|
// need to mark again to re-add read limit
|
||||||
|
is.mark(READ_LIMIT);
|
||||||
|
try {
|
||||||
|
// use BitmapFactory to get size
|
||||||
|
size = getSizeFromBitmap(is);
|
||||||
|
is.reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
||||||
|
*/
|
||||||
|
private Size getSizeFromExif(InputStream is) throws IOException {
|
||||||
|
ExifInterface exif = new ExifInterface(is);
|
||||||
|
// these can return 0 independent of default value
|
||||||
|
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
|
||||||
|
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
|
||||||
|
if (width == 0 || height == 0) return new Size();
|
||||||
|
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
|
||||||
|
if (orientation == ORIENTATION_ROTATE_90 ||
|
||||||
|
orientation == ORIENTATION_ROTATE_270 ||
|
||||||
|
orientation == ORIENTATION_TRANSVERSE ||
|
||||||
|
orientation == ORIENTATION_TRANSPOSE) {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
return new Size(height, width, "image/jpeg");
|
||||||
|
}
|
||||||
|
return new Size(width, height, "image/jpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of any image {@link InputStream}.
|
||||||
|
*/
|
||||||
|
private Size getSizeFromBitmap(InputStream is) {
|
||||||
|
DecodeResult result = imageHelper.decodeStream(is);
|
||||||
|
if (result.width < 1 || result.height < 1) return new Size();
|
||||||
|
return new Size(result.width, result.height, result.mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
class Size {
|
||||||
|
|
||||||
|
final int width;
|
||||||
|
final int height;
|
||||||
|
final String mimeType;
|
||||||
|
final boolean error;
|
||||||
|
|
||||||
|
Size(int width, int height, String mimeType) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size() {
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
this.mimeType = "";
|
||||||
|
this.error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ import static android.view.View.VISIBLE;
|
|||||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||||
|
|
||||||
public class RssFeedImportActivity extends BriarActivity {
|
public class RssFeedImportActivity extends BriarActivity {
|
||||||
|
|
||||||
@@ -77,7 +78,6 @@ public class RssFeedImportActivity extends BriarActivity {
|
|||||||
if (actionId == IME_ACTION_DONE && importButton.isEnabled() &&
|
if (actionId == IME_ACTION_DONE && importButton.isEnabled() &&
|
||||||
importButton.getVisibility() == VISIBLE) {
|
importButton.getVisibility() == VISIBLE) {
|
||||||
publish();
|
publish();
|
||||||
hideSoftKeyboard(urlInput);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -123,6 +123,7 @@ public class RssFeedImportActivity extends BriarActivity {
|
|||||||
// hide import button, show progress bar
|
// hide import button, show progress bar
|
||||||
importButton.setVisibility(GONE);
|
importButton.setVisibility(GONE);
|
||||||
progressBar.setVisibility(VISIBLE);
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
hideSoftKeyboard(urlInput);
|
||||||
|
|
||||||
String url = validateAndNormaliseUrl(urlInput.getText().toString());
|
String url = validateAndNormaliseUrl(urlInput.getText().toString());
|
||||||
if (url == null) throw new AssertionError();
|
if (url == null) throw new AssertionError();
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ package org.briarproject.briar.android.blog;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.TextView.OnEditorActionListener;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
@@ -44,7 +41,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_L
|
|||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class WriteBlogPostActivity extends BriarActivity
|
public class WriteBlogPostActivity extends BriarActivity
|
||||||
implements OnEditorActionListener, SendListener {
|
implements SendListener {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(WriteBlogPostActivity.class.getName());
|
Logger.getLogger(WriteBlogPostActivity.class.getName());
|
||||||
@@ -113,12 +110,6 @@ public class WriteBlogPostActivity extends BriarActivity
|
|||||||
component.inject(this);
|
component.inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
|
|
||||||
input.requestFocus();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text,
|
public void onSendClick(@Nullable String text,
|
||||||
List<AttachmentHeader> headers) {
|
List<AttachmentHeader> headers) {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FeatureFlags;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
@@ -57,7 +56,6 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu;
|
|
||||||
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
||||||
@@ -85,8 +83,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
EventBus eventBus;
|
EventBus eventBus;
|
||||||
@Inject
|
@Inject
|
||||||
AndroidNotificationManager notificationManager;
|
AndroidNotificationManager notificationManager;
|
||||||
@Inject
|
|
||||||
FeatureFlags featureFlags;
|
|
||||||
|
|
||||||
private ContactListAdapter adapter;
|
private ContactListAdapter adapter;
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
@@ -126,19 +122,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
container, false);
|
container, false);
|
||||||
|
|
||||||
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
|
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
|
||||||
if (featureFlags.shouldEnableRemoteContacts()) {
|
speedDial.addOnMenuItemClickListener(this);
|
||||||
speedDial.addOnMenuItemClickListener(this);
|
|
||||||
} else {
|
|
||||||
speedDial.setMenu(new FabSpeedDialMenu(contentView.getContext()));
|
|
||||||
speedDial.addOnStateChangeListener(open -> {
|
|
||||||
if (open) {
|
|
||||||
Intent intent = new Intent(getContext(),
|
|
||||||
ContactExchangeActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
speedDial.closeMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
OnContactClickListener<ContactListItem> onContactClickListener =
|
OnContactClickListener<ContactListItem> onContactClickListener =
|
||||||
(view, item) -> {
|
(view, item) -> {
|
||||||
@@ -169,9 +153,10 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
startActivity(i);
|
startActivity(i);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
adapter = new ContactListAdapter(getContext(), onContactClickListener);
|
adapter = new ContactListAdapter(requireContext(),
|
||||||
|
onContactClickListener);
|
||||||
list = contentView.findViewById(R.id.list);
|
list = contentView.findViewById(R.id.list);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getContext()));
|
list.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.setEmptyImage(R.drawable.ic_empty_state_contact_list);
|
list.setEmptyImage(R.drawable.ic_empty_state_contact_list);
|
||||||
list.setEmptyText(getString(R.string.no_contacts));
|
list.setEmptyText(getString(R.string.no_contacts));
|
||||||
@@ -267,7 +252,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
if (revision == adapter.getRevision()) {
|
if (revision == adapter.getRevision()) {
|
||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
if (contacts.isEmpty()) list.showData();
|
if (contacts.isEmpty()) list.showData();
|
||||||
else adapter.addAll(contacts);
|
else adapter.replaceAll(contacts);
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Concurrent update, reloading");
|
LOG.info("Concurrent update, reloading");
|
||||||
loadContacts();
|
loadContacts();
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import android.support.annotation.Nullable;
|
|||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.NoSuchPendingContactException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||||
@@ -118,4 +120,19 @@ public class AddContactViewModel extends AndroidViewModel {
|
|||||||
return addContactResult;
|
return addContactResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updatePendingContact(String name, PendingContact p) {
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
contactManager.removePendingContact(p.getId());
|
||||||
|
addContact(name);
|
||||||
|
} catch(NoSuchPendingContactException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
// no error in UI as pending contact was converted into contact
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
addContactResult.postValue(new LiveResult<>(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar.android.contact.add.remote;
|
package org.briarproject.briar.android.contact.add.remote;
|
||||||
|
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.arch.lifecycle.ViewModelProvider;
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
import android.arch.lifecycle.ViewModelProviders;
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
@@ -11,7 +12,9 @@ import android.support.v4.app.ShareCompat.IntentBuilder;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -34,7 +37,8 @@ import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
|||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class LinkExchangeFragment extends BaseFragment {
|
public class LinkExchangeFragment extends BaseFragment
|
||||||
|
implements OnGlobalLayoutListener {
|
||||||
|
|
||||||
private static final String TAG = LinkExchangeFragment.class.getName();
|
private static final String TAG = LinkExchangeFragment.class.getName();
|
||||||
|
|
||||||
@@ -90,9 +94,30 @@ public class LinkExchangeFragment extends BaseFragment {
|
|||||||
observeOnce(viewModel.getHandshakeLink(), this,
|
observeOnce(viewModel.getHandshakeLink(), this,
|
||||||
this::onHandshakeLinkLoaded);
|
this::onHandshakeLinkLoaded);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
ScrollView scrollView = (ScrollView) v;
|
||||||
|
// we need to wait for views to be laid out to get the heights
|
||||||
|
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
ScrollView scrollView = (ScrollView) requireNonNull(getView());
|
||||||
|
View layout = scrollView.getChildAt(0);
|
||||||
|
int scrollBy = layout.getHeight() - scrollView.getHeight();
|
||||||
|
if (scrollBy > 0) {
|
||||||
|
// smoothScrollTo() is too fast due to the transition animation
|
||||||
|
ObjectAnimator animator = ObjectAnimator
|
||||||
|
.ofInt(scrollView, "scrollY", scrollBy);
|
||||||
|
animator.setDuration(1000);
|
||||||
|
animator.start();
|
||||||
|
}
|
||||||
|
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void onHandshakeLinkLoaded(String link) {
|
private void onHandshakeLinkLoaded(String link) {
|
||||||
View v = requireNonNull(getView());
|
View v = requireNonNull(getView());
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,15 @@ package org.briarproject.briar.android.contact.add.remote;
|
|||||||
|
|
||||||
import android.arch.lifecycle.ViewModelProvider;
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
import android.arch.lifecycle.ViewModelProviders;
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.support.design.widget.TextInputEditText;
|
import android.support.design.widget.TextInputEditText;
|
||||||
import android.support.design.widget.TextInputLayout;
|
import android.support.design.widget.TextInputLayout;
|
||||||
|
import android.support.v7.app.AlertDialog.Builder;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -15,6 +20,10 @@ import android.widget.ProgressBar;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
|
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||||
|
import org.briarproject.bramble.api.db.PendingContactExistsException;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
@@ -24,9 +33,13 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
|
import static android.support.v4.content.ContextCompat.getDrawable;
|
||||||
|
import static android.support.v4.graphics.drawable.DrawableCompat.setTint;
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||||
|
|
||||||
@@ -82,19 +95,20 @@ public class NicknameFragment extends BaseFragment {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getNicknameOrNull() {
|
private String getNicknameOrNull() {
|
||||||
Editable name = contactNameInput.getText();
|
Editable text = contactNameInput.getText();
|
||||||
if (name == null || name.toString().trim().length() == 0) {
|
if (text == null || text.toString().trim().length() == 0) {
|
||||||
contactNameLayout.setError(getString(R.string.nickname_missing));
|
contactNameLayout.setError(getString(R.string.nickname_missing));
|
||||||
contactNameInput.requestFocus();
|
contactNameInput.requestFocus();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (utf8IsTooLong(name.toString(), MAX_AUTHOR_NAME_LENGTH)) {
|
String name = text.toString().trim();
|
||||||
|
if (utf8IsTooLong(name, MAX_AUTHOR_NAME_LENGTH)) {
|
||||||
contactNameLayout.setError(getString(R.string.name_too_long));
|
contactNameLayout.setError(getString(R.string.name_too_long));
|
||||||
contactNameInput.requestFocus();
|
contactNameInput.requestFocus();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
contactNameLayout.setError(null);
|
contactNameLayout.setError(null);
|
||||||
return name.toString().trim();
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAddButtonClicked() {
|
private void onAddButtonClicked() {
|
||||||
@@ -106,23 +120,95 @@ public class NicknameFragment extends BaseFragment {
|
|||||||
|
|
||||||
viewModel.getAddContactResult().observe(this, result -> {
|
viewModel.getAddContactResult().observe(this, result -> {
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
if (result.hasError()) {
|
if (result.hasError())
|
||||||
int stringRes;
|
handleException(name, requireNonNull(result.getException()));
|
||||||
if (result
|
else
|
||||||
.getException() instanceof UnsupportedVersionException) {
|
showPendingContactListActivity();
|
||||||
stringRes = R.string.unsupported_link;
|
|
||||||
} else {
|
|
||||||
stringRes = R.string.adding_contact_error;
|
|
||||||
}
|
|
||||||
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
|
||||||
} else {
|
|
||||||
Intent intent = new Intent(getActivity(),
|
|
||||||
PendingContactListActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
});
|
});
|
||||||
viewModel.addContact(name);
|
viewModel.addContact(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showPendingContactListActivity() {
|
||||||
|
Intent intent = new Intent(getActivity(),
|
||||||
|
PendingContactListActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleException(String name, Exception e) {
|
||||||
|
if (e instanceof ContactExistsException) {
|
||||||
|
ContactExistsException ce = (ContactExistsException) e;
|
||||||
|
handleExistingContact(name, ce.getRemoteAuthor());
|
||||||
|
} else if (e instanceof PendingContactExistsException) {
|
||||||
|
PendingContactExistsException pe =
|
||||||
|
(PendingContactExistsException) e;
|
||||||
|
handleExistingPendingContact(name, pe.getPendingContact());
|
||||||
|
} else if (e instanceof UnsupportedVersionException) {
|
||||||
|
int stringRes = R.string.unsupported_link;
|
||||||
|
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
int stringRes = R.string.adding_contact_error;
|
||||||
|
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleExistingContact(String name, Author existing) {
|
||||||
|
OnClickListener listener = (d, w) -> {
|
||||||
|
d.dismiss();
|
||||||
|
String str = getString(R.string.contact_already_exists, name);
|
||||||
|
Toast.makeText(getContext(), str, LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
showSameLinkDialog(existing.getName(), name,
|
||||||
|
R.string.duplicate_link_dialog_text_1_contact, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleExistingPendingContact(String name, PendingContact p) {
|
||||||
|
OnClickListener listener = (d, w) -> {
|
||||||
|
viewModel.updatePendingContact(name, p);
|
||||||
|
Toast.makeText(getContext(), R.string.pending_contact_updated_toast,
|
||||||
|
LENGTH_LONG).show();
|
||||||
|
d.dismiss();
|
||||||
|
showPendingContactListActivity();
|
||||||
|
};
|
||||||
|
showSameLinkDialog(p.getAlias(), name,
|
||||||
|
R.string.duplicate_link_dialog_text_1, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSameLinkDialog(String name1, String name2,
|
||||||
|
@StringRes int existsRes, OnClickListener samePersonListener) {
|
||||||
|
Context ctx = requireContext();
|
||||||
|
Builder b = new Builder(ctx, R.style.BriarDialogTheme_Neutral);
|
||||||
|
b.setTitle(getString(R.string.duplicate_link_dialog_title));
|
||||||
|
String msg = getString(existsRes, name1) + "\n\n" +
|
||||||
|
getString(R.string.duplicate_link_dialog_text_2, name2, name1);
|
||||||
|
b.setMessage(msg);
|
||||||
|
b.setPositiveButton(R.string.same_person_button, samePersonListener);
|
||||||
|
b.setNegativeButton(R.string.different_person_button, (d, w) -> {
|
||||||
|
d.dismiss();
|
||||||
|
showWarningDialog(name1, name2);
|
||||||
|
});
|
||||||
|
b.setCancelable(false);
|
||||||
|
b.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showWarningDialog(String name1, String name2) {
|
||||||
|
Context ctx = requireContext();
|
||||||
|
Builder b = new Builder(ctx, R.style.BriarDialogTheme);
|
||||||
|
Drawable icon = getDrawable(ctx, R.drawable.alerts_and_states_error);
|
||||||
|
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
|
||||||
|
b.setIcon(icon);
|
||||||
|
b.setTitle(getString(R.string.duplicate_link_dialog_title));
|
||||||
|
b.setMessage(
|
||||||
|
getString(R.string.duplicate_link_dialog_text_3, name1, name2));
|
||||||
|
b.setPositiveButton(R.string.ok, (d, w) -> {
|
||||||
|
d.dismiss();
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
b.setCancelable(false);
|
||||||
|
b.show();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class PendingContactListViewModel extends AndroidViewModel
|
|||||||
Collection<Pair<PendingContact, PendingContactState>> pairs =
|
Collection<Pair<PendingContact, PendingContactState>> pairs =
|
||||||
contactManager.getPendingContacts();
|
contactManager.getPendingContacts();
|
||||||
List<PendingContactItem> items = new ArrayList<>(pairs.size());
|
List<PendingContactItem> items = new ArrayList<>(pairs.size());
|
||||||
boolean online = false;
|
boolean online = items.isEmpty();
|
||||||
for (Pair<PendingContact, PendingContactState> pair : pairs) {
|
for (Pair<PendingContact, PendingContactState> pair : pairs) {
|
||||||
PendingContact p = pair.getFirst();
|
PendingContact p = pair.getFirst();
|
||||||
PendingContactState state = pair.getSecond();
|
PendingContactState state = pair.getSecond();
|
||||||
|
|||||||
@@ -21,9 +21,12 @@ import org.briarproject.briar.android.activity.BaseActivity;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -76,13 +79,14 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
|||||||
setButton.setOnClickListener(v1 -> onSetButtonClicked());
|
setButton.setOnClickListener(v1 -> onSetButtonClicked());
|
||||||
|
|
||||||
Button cancelButton = v.findViewById(R.id.cancelButton);
|
Button cancelButton = v.findViewById(R.id.cancelButton);
|
||||||
cancelButton.setOnClickListener(v1 -> getDialog().cancel());
|
cancelButton.setOnClickListener(v1 -> onCancelButtonClicked());
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSetButtonClicked() {
|
private void onSetButtonClicked() {
|
||||||
String alias = aliasEditText.getText().toString();
|
hideSoftKeyboard(aliasEditText);
|
||||||
|
String alias = aliasEditText.getText().toString().trim();
|
||||||
if (toUtf8(alias).length > MAX_AUTHOR_NAME_LENGTH) {
|
if (toUtf8(alias).length > MAX_AUTHOR_NAME_LENGTH) {
|
||||||
aliasEditLayout.setError(getString(R.string.name_too_long));
|
aliasEditLayout.setError(getString(R.string.name_too_long));
|
||||||
} else {
|
} else {
|
||||||
@@ -91,4 +95,17 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onCancelButtonClicked() {
|
||||||
|
hideSoftKeyboard(aliasEditText);
|
||||||
|
getDialog().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
requireNonNull(getDialog().getWindow())
|
||||||
|
.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
|
showSoftKeyboard(aliasEditText);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
|
import org.briarproject.bramble.api.db.NoSuchMessageException;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
@@ -81,6 +82,7 @@ import org.briarproject.briar.api.messaging.Attachment;
|
|||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -107,10 +109,12 @@ import static android.support.v7.util.SortedList.INVALID_POSITION;
|
|||||||
import static android.view.Gravity.RIGHT;
|
import static android.view.Gravity.RIGHT;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.sort;
|
import static java.util.Collections.sort;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
@@ -138,7 +142,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ConversationActivity.class.getName());
|
getLogger(ConversationActivity.class.getName());
|
||||||
|
|
||||||
private static final int TRANSITION_DURATION_MS = 500;
|
private static final int TRANSITION_DURATION_MS = 500;
|
||||||
private static final int ONBOARDING_DELAY_MS = 250;
|
private static final int ONBOARDING_DELAY_MS = 250;
|
||||||
@@ -171,6 +175,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
volatile GroupInvitationManager groupInvitationManager;
|
volatile GroupInvitationManager groupInvitationManager;
|
||||||
|
|
||||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||||
|
private final Map<MessageId, PrivateMessageHeader> missingAttachments =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
private final Observer<String> contactNameObserver = name -> {
|
private final Observer<String> contactNameObserver = name -> {
|
||||||
requireNonNull(name);
|
requireNonNull(name);
|
||||||
loadMessages();
|
loadMessages();
|
||||||
@@ -270,7 +276,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
textInputView.setSendController(sendController);
|
textInputView.setSendController(sendController);
|
||||||
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
|
||||||
textInputView.setReady(false);
|
textInputView.setReady(false);
|
||||||
textInputView.addOnKeyboardShownListener(this::scrollToBottom);
|
textInputView.setOnKeyboardShownListener(this::scrollToBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scrollToBottom() {
|
private void scrollToBottom() {
|
||||||
@@ -350,6 +356,11 @@ public class ConversationActivity extends BriarActivity
|
|||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
inflater.inflate(R.menu.conversation_actions, menu);
|
inflater.inflate(R.menu.conversation_actions, menu);
|
||||||
|
|
||||||
|
// Hide private message deletion action if feature is not enabled
|
||||||
|
if (!featureFlags.shouldEnablePrivateMessageDeletion()) {
|
||||||
|
menu.removeItem(R.id.action_delete_all_messages);
|
||||||
|
}
|
||||||
|
|
||||||
// enable introduction action if available
|
// enable introduction action if available
|
||||||
observeOnce(viewModel.showIntroductionAction(), this, enable -> {
|
observeOnce(viewModel.showIntroductionAction(), this, enable -> {
|
||||||
if (enable != null && enable) {
|
if (enable != null && enable) {
|
||||||
@@ -383,6 +394,9 @@ public class ConversationActivity extends BriarActivity
|
|||||||
AliasDialogFragment.newInstance().show(
|
AliasDialogFragment.newInstance().show(
|
||||||
getSupportFragmentManager(), AliasDialogFragment.TAG);
|
getSupportFragmentManager(), AliasDialogFragment.TAG);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_delete_all_messages:
|
||||||
|
askToDeleteAllMessages();
|
||||||
|
return true;
|
||||||
case R.id.action_social_remove_person:
|
case R.id.action_social_remove_person:
|
||||||
askToRemoveContact();
|
askToRemoveContact();
|
||||||
return true;
|
return true;
|
||||||
@@ -434,29 +448,40 @@ public class ConversationActivity extends BriarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void eagerlyLoadMessageSize(PrivateMessageHeader h)
|
private void eagerlyLoadMessageSize(PrivateMessageHeader h) {
|
||||||
throws DbException {
|
try {
|
||||||
MessageId id = h.getId();
|
MessageId id = h.getId();
|
||||||
// If the message has text, load it
|
// If the message has text, load it
|
||||||
if (h.hasText()) {
|
if (h.hasText()) {
|
||||||
String text = textCache.get(id);
|
String text = textCache.get(id);
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
LOG.info("Eagerly loading text for latest message");
|
LOG.info("Eagerly loading text for latest message");
|
||||||
text = messagingManager.getMessageText(id);
|
text = messagingManager.getMessageText(id);
|
||||||
textCache.put(id, requireNonNull(text));
|
textCache.put(id, requireNonNull(text));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// If the message has a single image, load its size - for multiple
|
||||||
// If the message has a single image, load its size - for multiple
|
// images we use a grid so the size is fixed
|
||||||
// images we use a grid so the size is fixed
|
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||||
if (h.getAttachmentHeaders().size() == 1) {
|
if (headers.size() == 1) {
|
||||||
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
||||||
if (items == null) {
|
if (items == null) {
|
||||||
LOG.info("Eagerly loading image size for latest message");
|
LOG.info("Eagerly loading image size for latest message");
|
||||||
items = attachmentRetriever.getAttachmentItems(
|
AttachmentHeader header = headers.get(0);
|
||||||
attachmentRetriever.getMessageAttachments(
|
try {
|
||||||
h.getAttachmentHeaders()));
|
Attachment a = attachmentRetriever
|
||||||
attachmentRetriever.cachePut(id, items);
|
.getMessageAttachment(header);
|
||||||
|
AttachmentItem item =
|
||||||
|
attachmentRetriever.getAttachmentItem(a, true);
|
||||||
|
attachmentRetriever.cachePut(id, singletonList(item));
|
||||||
|
} catch (NoSuchMessageException e) {
|
||||||
|
LOG.info("Attachment not received yet");
|
||||||
|
missingAttachments.put(header.getMessageId(), h);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,16 +559,30 @@ public class ConversationActivity extends BriarActivity
|
|||||||
&& adapter.isScrolledToBottom(layoutManager);
|
&& adapter.isScrolledToBottom(layoutManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMessageAttachments(MessageId messageId,
|
private void loadMessageAttachments(PrivateMessageHeader h) {
|
||||||
List<AttachmentHeader> headers) {
|
// TODO: Use placeholders for missing/invalid attachments
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
|
||||||
attachmentRetriever.getMessageAttachments(headers);
|
|
||||||
// TODO move getting the items off to IoExecutor, if size == 1
|
// TODO move getting the items off to IoExecutor, if size == 1
|
||||||
List<AttachmentItem> items =
|
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||||
attachmentRetriever.getAttachmentItems(attachments);
|
boolean needsSize = headers.size() == 1;
|
||||||
displayMessageAttachments(messageId, items);
|
List<AttachmentItem> items = new ArrayList<>(headers.size());
|
||||||
|
for (AttachmentHeader header : headers) {
|
||||||
|
try {
|
||||||
|
Attachment a = attachmentRetriever
|
||||||
|
.getMessageAttachment(header);
|
||||||
|
AttachmentItem item = attachmentRetriever
|
||||||
|
.getAttachmentItem(a, needsSize);
|
||||||
|
items.add(item);
|
||||||
|
} catch (NoSuchMessageException e) {
|
||||||
|
LOG.info("Attachment not received yet");
|
||||||
|
missingAttachments.put(header.getMessageId(), h);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't cache items unless all are present and valid
|
||||||
|
attachmentRetriever.cachePut(h.getId(), items);
|
||||||
|
displayMessageAttachments(h.getId(), items);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -553,7 +592,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void displayMessageAttachments(MessageId m,
|
private void displayMessageAttachments(MessageId m,
|
||||||
List<AttachmentItem> items) {
|
List<AttachmentItem> items) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
attachmentRetriever.cachePut(m, items);
|
|
||||||
Pair<Integer, ConversationMessageItem> pair =
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
adapter.getMessageItem(m);
|
adapter.getMessageItem(m);
|
||||||
if (pair != null) {
|
if (pair != null) {
|
||||||
@@ -567,6 +605,13 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof AttachmentReceivedEvent) {
|
||||||
|
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
|
||||||
|
if (a.getContactId().equals(contactId)) {
|
||||||
|
LOG.info("Attachment received");
|
||||||
|
onAttachmentReceived(a.getMessageId());
|
||||||
|
}
|
||||||
|
}
|
||||||
if (e instanceof ContactRemovedEvent) {
|
if (e instanceof ContactRemovedEvent) {
|
||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
if (c.getContactId().equals(contactId)) {
|
if (c.getContactId().equals(contactId)) {
|
||||||
@@ -617,6 +662,15 @@ public class ConversationActivity extends BriarActivity
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onAttachmentReceived(MessageId attachmentId) {
|
||||||
|
PrivateMessageHeader h = missingAttachments.remove(attachmentId);
|
||||||
|
if (h != null) {
|
||||||
|
LOG.info("Missing attachment received");
|
||||||
|
loadMessageAttachments(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void onNewConversationMessage(ConversationMessageHeader h) {
|
private void onNewConversationMessage(ConversationMessageHeader h) {
|
||||||
if (h instanceof ConversationRequest ||
|
if (h instanceof ConversationRequest ||
|
||||||
@@ -681,6 +735,52 @@ public class ConversationActivity extends BriarActivity
|
|||||||
addConversationItem(h.accept(visitor));
|
addConversationItem(h.accept(visitor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void askToDeleteAllMessages() {
|
||||||
|
AlertDialog.Builder builder =
|
||||||
|
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||||
|
builder.setTitle(getString(R.string.dialog_title_delete_all_messages));
|
||||||
|
builder.setMessage(
|
||||||
|
getString(R.string.dialog_message_delete_all_messages));
|
||||||
|
builder.setNegativeButton(R.string.delete,
|
||||||
|
(dialog, which) -> deleteAllMessages());
|
||||||
|
builder.setPositiveButton(R.string.cancel, null);
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteAllMessages() {
|
||||||
|
list.showProgressBar();
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
try {
|
||||||
|
boolean allDeleted =
|
||||||
|
conversationManager.deleteAllMessages(contactId);
|
||||||
|
reloadConversationAfterDeletingAllMessages(allDeleted);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
runOnUiThreadUnlessDestroyed(() -> list.showData());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadConversationAfterDeletingAllMessages(
|
||||||
|
boolean allDeleted) {
|
||||||
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
|
adapter.clear();
|
||||||
|
loadMessages();
|
||||||
|
if (!allDeleted) showNotAllDeletedDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showNotAllDeletedDialog() {
|
||||||
|
AlertDialog.Builder builder =
|
||||||
|
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
|
||||||
|
builder.setTitle(
|
||||||
|
getString(R.string.dialog_title_not_all_messages_deleted));
|
||||||
|
builder.setMessage(
|
||||||
|
getString(R.string.dialog_message_not_all_messages_deleted));
|
||||||
|
builder.setPositiveButton(R.string.ok, null);
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
private void askToRemoveContact() {
|
private void askToRemoveContact() {
|
||||||
DialogInterface.OnClickListener okListener =
|
DialogInterface.OnClickListener okListener =
|
||||||
(dialog, which) -> removeContact();
|
(dialog, which) -> removeContact();
|
||||||
@@ -903,11 +1003,11 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
public List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h) {
|
||||||
List<AttachmentHeader> headers) {
|
List<AttachmentItem> attachments =
|
||||||
List<AttachmentItem> attachments = attachmentRetriever.cacheGet(m);
|
attachmentRetriever.cacheGet(h.getId());
|
||||||
if (attachments == null) {
|
if (attachments == null) {
|
||||||
loadMessageAttachments(m, headers);
|
loadMessageAttachments(h);
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
return attachments;
|
return attachments;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
@@ -51,7 +50,6 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
|
|
||||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||||
|
|
||||||
@@ -101,10 +99,13 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
@Inject
|
@Inject
|
||||||
ConversationViewModel(Application application,
|
ConversationViewModel(Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@IoExecutor Executor ioExecutor, TransactionManager db,
|
TransactionManager db,
|
||||||
MessagingManager messagingManager, ContactManager contactManager,
|
MessagingManager messagingManager,
|
||||||
|
ContactManager contactManager,
|
||||||
SettingsManager settingsManager,
|
SettingsManager settingsManager,
|
||||||
PrivateMessageFactory privateMessageFactory) {
|
PrivateMessageFactory privateMessageFactory,
|
||||||
|
AttachmentRetriever attachmentRetriever,
|
||||||
|
AttachmentCreator attachmentCreator) {
|
||||||
super(application);
|
super(application);
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@@ -112,10 +113,8 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.privateMessageFactory = privateMessageFactory;
|
this.privateMessageFactory = privateMessageFactory;
|
||||||
this.attachmentRetriever = new AttachmentRetriever(messagingManager,
|
this.attachmentRetriever = attachmentRetriever;
|
||||||
getAttachmentDimensions(application.getResources()));
|
this.attachmentCreator = attachmentCreator;
|
||||||
this.attachmentCreator = new AttachmentCreator(getApplication(),
|
|
||||||
ioExecutor, messagingManager, attachmentRetriever);
|
|
||||||
messagingGroupId = Transformations
|
messagingGroupId = Transformations
|
||||||
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||||
contactDeleted.setValue(false);
|
contactDeleted.setValue(false);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.briarproject.briar.api.forum.ForumInvitationRequest;
|
|||||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
||||||
@@ -56,8 +55,7 @@ class ConversationVisitor implements
|
|||||||
if (h.getAttachmentHeaders().isEmpty()) {
|
if (h.getAttachmentHeaders().isEmpty()) {
|
||||||
attachments = emptyList();
|
attachments = emptyList();
|
||||||
} else {
|
} else {
|
||||||
attachments = attachmentCache
|
attachments = attachmentCache.getAttachmentItems(h);
|
||||||
.getAttachmentItems(h.getId(), h.getAttachmentHeaders());
|
|
||||||
}
|
}
|
||||||
if (h.isLocal()) {
|
if (h.isLocal()) {
|
||||||
item = new ConversationMessageItem(
|
item = new ConversationMessageItem(
|
||||||
@@ -295,7 +293,6 @@ class ConversationVisitor implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AttachmentCache {
|
interface AttachmentCache {
|
||||||
List<AttachmentItem> getAttachmentItems(MessageId m,
|
List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h);
|
||||||
List<AttachmentHeader> headers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,11 +142,8 @@ public class ImageActivity extends BriarActivity
|
|||||||
viewPager.setAdapter(pagerAdapter);
|
viewPager.setAdapter(pagerAdapter);
|
||||||
viewPager.setCurrentItem(position);
|
viewPager.setCurrentItem(position);
|
||||||
|
|
||||||
if (SDK_INT >= 16) {
|
viewModel.getOnImageClicked().observeEvent(this, this::onImageClicked);
|
||||||
viewModel.getOnImageClicked()
|
window.getDecorView().setSystemUiVisibility(UI_FLAGS_DEFAULT);
|
||||||
.observeEvent(this, this::onImageClicked);
|
|
||||||
window.getDecorView().setSystemUiVisibility(UI_FLAGS_DEFAULT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -174,11 +171,7 @@ public class ImageActivity extends BriarActivity
|
|||||||
viewModel.setToolbarPosition(
|
viewModel.setToolbarPosition(
|
||||||
appBarLayout.getTop(), appBarLayout.getBottom()
|
appBarLayout.getTop(), appBarLayout.getBottom()
|
||||||
);
|
);
|
||||||
if (SDK_INT >= 16) {
|
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||||
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
|
||||||
} else {
|
|
||||||
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -265,7 +258,7 @@ public class ImageActivity extends BriarActivity
|
|||||||
* when the previous activity (with visible status bar) is shown.
|
* when the previous activity (with visible status bar) is shown.
|
||||||
*/
|
*/
|
||||||
private void showStatusBarBeforeFinishing() {
|
private void showStatusBarBeforeFinishing() {
|
||||||
if (SDK_INT >= 16 && appBarLayout.getVisibility() == GONE) {
|
if (appBarLayout.getVisibility() == GONE) {
|
||||||
View decorView = getWindow().getDecorView();
|
View decorView = getWindow().getDecorView();
|
||||||
decorView.setSystemUiVisibility(UI_FLAGS_DEFAULT);
|
decorView.setSystemUiVisibility(UI_FLAGS_DEFAULT);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
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;
|
||||||
@@ -135,10 +134,10 @@ public class ImageViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
|
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
|
||||||
@Nullable Runnable afterCopy) {
|
@Nullable Runnable afterCopy) {
|
||||||
MessageId messageId = attachment.getMessageId();
|
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
Attachment a = messagingManager.getAttachment(messageId);
|
Attachment a =
|
||||||
|
messagingManager.getAttachment(attachment.getHeader());
|
||||||
copyImageFromDb(a, osp, afterCopy);
|
copyImageFromDb(a, osp, afterCopy);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import com.bumptech.glide.load.data.DataFetcher;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -50,11 +50,12 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
|
|||||||
@Override
|
@Override
|
||||||
public void loadData(Priority priority,
|
public void loadData(Priority priority,
|
||||||
DataCallback<? super InputStream> callback) {
|
DataCallback<? super InputStream> callback) {
|
||||||
MessageId id = attachment.getMessageId();
|
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
if (cancel) return;
|
if (cancel) return;
|
||||||
try {
|
try {
|
||||||
inputStream = messagingManager.getAttachment(id).getStream();
|
Attachment a =
|
||||||
|
messagingManager.getAttachment(attachment.getHeader());
|
||||||
|
inputStream = a.getStream();
|
||||||
callback.onDataReady(inputStream);
|
callback.onDataReady(inputStream);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
callback.onLoadFailed(e);
|
callback.onLoadFailed(e);
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
|
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -91,12 +93,6 @@ public class CreateForumActivity extends BriarActivity {
|
|||||||
progress = findViewById(R.id.createForumProgressBar);
|
progress = findViewById(R.id.createForumProgressBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
showSoftKeyboard(nameEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void injectActivity(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ public class ForumListFragment extends BaseEventFragment implements
|
|||||||
if (revision == adapter.getRevision()) {
|
if (revision == adapter.getRevision()) {
|
||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
if (forums.isEmpty()) list.showData();
|
if (forums.isEmpty()) list.showData();
|
||||||
else adapter.addAll(forums);
|
else adapter.replaceAll(forums);
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Concurrent update, reloading");
|
LOG.info("Concurrent update, reloading");
|
||||||
loadForums();
|
loadForums();
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import static java.util.Objects.requireNonNull;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
|
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -184,7 +185,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
introductionActivity.hideSoftKeyboard(ui.message);
|
hideSoftKeyboard(ui.message);
|
||||||
introductionActivity.onBackPressed();
|
introductionActivity.onBackPressed();
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
@@ -201,7 +202,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
makeIntroduction(contact1, contact2, text);
|
makeIntroduction(contact1, contact2, text);
|
||||||
|
|
||||||
// don't wait for the introduction to be made before finishing activity
|
// don't wait for the introduction to be made before finishing activity
|
||||||
introductionActivity.hideSoftKeyboard(ui.message);
|
hideSoftKeyboard(ui.message);
|
||||||
introductionActivity.setResult(RESULT_OK);
|
introductionActivity.setResult(RESULT_OK);
|
||||||
introductionActivity.supportFinishAfterTransition();
|
introductionActivity.supportFinishAfterTransition();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED;
|
|||||||
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
|
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
|
||||||
import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
|
import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
|
||||||
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
|
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
@@ -340,7 +339,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void setVideoStabilisation(Parameters params) {
|
private void setVideoStabilisation(Parameters params) {
|
||||||
if (SDK_INT >= 15 && params.isVideoStabilizationSupported()) {
|
if (params.isVideoStabilizationSupported()) {
|
||||||
params.setVideoStabilization(true);
|
params.setVideoStabilization(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,10 +414,8 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
if (SDK_INT >= 15) {
|
LOG.info("Video stabilisation enabled: "
|
||||||
LOG.info("Video stabilisation enabled: "
|
+ params.getVideoStabilization());
|
||||||
+ params.getVideoStabilization());
|
|
||||||
}
|
|
||||||
LOG.info("Scene mode: " + params.getSceneMode());
|
LOG.info("Scene mode: " + params.getSceneMode());
|
||||||
LOG.info("Focus mode: " + params.getFocusMode());
|
LOG.info("Focus mode: " + params.getFocusMode());
|
||||||
LOG.info("Flash mode: " + params.getFlashMode());
|
LOG.info("Flash mode: " + params.getFlashMode());
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
|
|||||||
@UiThread
|
@UiThread
|
||||||
private void contactExchangeSucceeded(Author remoteAuthor) {
|
private void contactExchangeSucceeded(Author remoteAuthor) {
|
||||||
String contactName = remoteAuthor.getName();
|
String contactName = remoteAuthor.getName();
|
||||||
String format = getString(R.string.contact_added_toast);
|
String text = getString(R.string.contact_added_toast, contactName);
|
||||||
String text = String.format(format, contactName);
|
|
||||||
Toast.makeText(this, text, LENGTH_LONG).show();
|
Toast.makeText(this, text, LENGTH_LONG).show();
|
||||||
supportFinishAfterTransition();
|
supportFinishAfterTransition();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,10 +183,14 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
if (bt == null) {
|
if (bt == null) {
|
||||||
setBluetoothState(BluetoothState.NO_ADAPTER);
|
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||||
} else {
|
} else {
|
||||||
setBluetoothState(BluetoothState.WAITING);
|
|
||||||
wasAdapterEnabled = bt.isEnabled();
|
|
||||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||||
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
|
if (i.resolveActivity(getPackageManager()) != null) {
|
||||||
|
setBluetoothState(BluetoothState.WAITING);
|
||||||
|
wasAdapterEnabled = bt.isEnabled();
|
||||||
|
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
|
||||||
|
} else {
|
||||||
|
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import javax.inject.Inject;
|
|||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
||||||
|
|
||||||
public class ChangePasswordActivity extends BriarActivity
|
public class ChangePasswordActivity extends BriarActivity
|
||||||
implements OnClickListener, OnEditorActionListener {
|
implements OnClickListener, OnEditorActionListener {
|
||||||
|
|||||||
@@ -83,12 +83,6 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
showSoftKeyboard(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||||
int after) {
|
int after) {
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ public class GroupActivity extends
|
|||||||
@Inject
|
@Inject
|
||||||
GroupController controller;
|
GroupController controller;
|
||||||
|
|
||||||
private boolean isCreator, isDissolved = false;
|
@Nullable
|
||||||
|
private Boolean isCreator = null;
|
||||||
|
private boolean isDissolved = false;
|
||||||
private MenuItem revealMenuItem, inviteMenuItem, leaveMenuItem,
|
private MenuItem revealMenuItem, inviteMenuItem, leaveMenuItem,
|
||||||
dissolveMenuItem;
|
dissolveMenuItem;
|
||||||
|
|
||||||
@@ -137,6 +139,14 @@ public class GroupActivity extends
|
|||||||
inviteMenuItem = menu.findItem(R.id.action_group_invite);
|
inviteMenuItem = menu.findItem(R.id.action_group_invite);
|
||||||
leaveMenuItem = menu.findItem(R.id.action_group_leave);
|
leaveMenuItem = menu.findItem(R.id.action_group_leave);
|
||||||
dissolveMenuItem = menu.findItem(R.id.action_group_dissolve);
|
dissolveMenuItem = menu.findItem(R.id.action_group_dissolve);
|
||||||
|
|
||||||
|
// all role-dependent items are invisible until we know our role
|
||||||
|
revealMenuItem.setVisible(false);
|
||||||
|
inviteMenuItem.setVisible(false);
|
||||||
|
leaveMenuItem.setVisible(false);
|
||||||
|
dissolveMenuItem.setVisible(false);
|
||||||
|
|
||||||
|
// show items based on role
|
||||||
showMenuItems();
|
showMenuItems();
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
@@ -151,19 +161,27 @@ public class GroupActivity extends
|
|||||||
startActivity(i1);
|
startActivity(i1);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_group_reveal:
|
case R.id.action_group_reveal:
|
||||||
|
if (isCreator == null || isCreator)
|
||||||
|
throw new IllegalStateException();
|
||||||
Intent i2 = new Intent(this, RevealContactsActivity.class);
|
Intent i2 = new Intent(this, RevealContactsActivity.class);
|
||||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
startActivity(i2);
|
startActivity(i2);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_group_invite:
|
case R.id.action_group_invite:
|
||||||
|
if (isCreator == null || !isCreator)
|
||||||
|
throw new IllegalStateException();
|
||||||
Intent i3 = new Intent(this, GroupInviteActivity.class);
|
Intent i3 = new Intent(this, GroupInviteActivity.class);
|
||||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
startActivityForResult(i3, REQUEST_GROUP_INVITE);
|
startActivityForResult(i3, REQUEST_GROUP_INVITE);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_group_leave:
|
case R.id.action_group_leave:
|
||||||
|
if (isCreator == null || isCreator)
|
||||||
|
throw new IllegalStateException();
|
||||||
showLeaveGroupDialog();
|
showLeaveGroupDialog();
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_group_dissolve:
|
case R.id.action_group_dissolve:
|
||||||
|
if (isCreator == null || !isCreator)
|
||||||
|
throw new IllegalStateException();
|
||||||
showDissolveGroupDialog();
|
showDissolveGroupDialog();
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
@@ -209,18 +227,12 @@ public class GroupActivity extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showMenuItems() {
|
private void showMenuItems() {
|
||||||
if (leaveMenuItem == null || dissolveMenuItem == null) return;
|
// we need to have the menu items and know if we are the creator
|
||||||
if (isCreator) {
|
if (leaveMenuItem == null || isCreator == null) return;
|
||||||
revealMenuItem.setVisible(false);
|
revealMenuItem.setVisible(!isCreator);
|
||||||
inviteMenuItem.setVisible(true);
|
inviteMenuItem.setVisible(isCreator);
|
||||||
leaveMenuItem.setVisible(false);
|
leaveMenuItem.setVisible(!isCreator);
|
||||||
dissolveMenuItem.setVisible(true);
|
dissolveMenuItem.setVisible(isCreator);
|
||||||
} else {
|
|
||||||
revealMenuItem.setVisible(true);
|
|
||||||
inviteMenuItem.setVisible(false);
|
|
||||||
leaveMenuItem.setVisible(true);
|
|
||||||
dissolveMenuItem.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLeaveGroupDialog() {
|
private void showLeaveGroupDialog() {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import static android.view.View.GONE;
|
|||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
|
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -91,12 +92,6 @@ public class CreateGroupFragment extends BaseFragment {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
listener.showSoftKeyboard(nameEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUniqueTag() {
|
public String getUniqueTag() {
|
||||||
return TAG;
|
return TAG;
|
||||||
@@ -120,7 +115,7 @@ public class CreateGroupFragment extends BaseFragment {
|
|||||||
|
|
||||||
private void createGroup() {
|
private void createGroup() {
|
||||||
if (!validateName()) return;
|
if (!validateName()) return;
|
||||||
listener.hideSoftKeyboard(nameEntry);
|
hideSoftKeyboard(nameEntry);
|
||||||
createGroupButton.setVisibility(GONE);
|
createGroupButton.setVisibility(GONE);
|
||||||
progress.setVisibility(VISIBLE);
|
progress.setVisibility(VISIBLE);
|
||||||
listener.onGroupNameChosen(nameEntry.getText().toString());
|
listener.onGroupNameChosen(nameEntry.getText().toString());
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
package org.briarproject.briar.android.privategroup.creation;
|
package org.briarproject.briar.android.privategroup.creation;
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
|
|
||||||
interface CreateGroupListener extends BaseFragmentListener {
|
interface CreateGroupListener extends BaseFragmentListener {
|
||||||
|
|
||||||
void onGroupNameChosen(String name);
|
void onGroupNameChosen(String name);
|
||||||
|
|
||||||
void showSoftKeyboard(View view);
|
|
||||||
|
|
||||||
void hideSoftKeyboard(View view);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public class GroupListFragment extends BaseFragment implements
|
|||||||
if (revision == adapter.getRevision()) {
|
if (revision == adapter.getRevision()) {
|
||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
if (groups.isEmpty()) list.showData();
|
if (groups.isEmpty()) list.showData();
|
||||||
else adapter.addAll(groups);
|
else adapter.replaceAll(groups);
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Concurrent update, reloading");
|
LOG.info("Concurrent update, reloading");
|
||||||
loadGroups();
|
loadGroups();
|
||||||
|
|||||||
@@ -90,14 +90,9 @@ public class BriarReportPrimer implements ReportPrimer {
|
|||||||
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
|
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
|
||||||
am.getMemoryInfo(mem);
|
am.getMemoryInfo(mem);
|
||||||
String systemMemory;
|
String systemMemory;
|
||||||
if (Build.VERSION.SDK_INT >= 16) {
|
systemMemory = (mem.totalMem / 1024 / 1024) + " MiB total, "
|
||||||
systemMemory = (mem.totalMem / 1024 / 1024) + " MiB total, "
|
+ (mem.availMem / 1024 / 1204) + " MiB free, "
|
||||||
+ (mem.availMem / 1024 / 1204) + " MiB free, "
|
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
||||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
|
||||||
} else {
|
|
||||||
systemMemory = (mem.availMem / 1024 / 1204) + " MiB free, "
|
|
||||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
|
||||||
}
|
|
||||||
customData.put("System memory", systemMemory);
|
customData.put("System memory", systemMemory);
|
||||||
|
|
||||||
// Virtual machine memory
|
// Virtual machine memory
|
||||||
|
|||||||
@@ -218,11 +218,17 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (SDK_INT < 27) {
|
if (SDK_INT < 27) {
|
||||||
// remove System Default Theme option
|
// remove System Default Theme option from preference entries
|
||||||
|
// as it is not functional on this API anyway
|
||||||
List<CharSequence> entries =
|
List<CharSequence> entries =
|
||||||
new ArrayList<>(Arrays.asList(theme.getEntries()));
|
new ArrayList<>(Arrays.asList(theme.getEntries()));
|
||||||
entries.remove(getString(R.string.pref_theme_system));
|
entries.remove(getString(R.string.pref_theme_system));
|
||||||
theme.setEntries(entries.toArray(new CharSequence[0]));
|
theme.setEntries(entries.toArray(new CharSequence[0]));
|
||||||
|
// also remove corresponding value
|
||||||
|
List<CharSequence> values =
|
||||||
|
new ArrayList<>(Arrays.asList(theme.getEntryValues()));
|
||||||
|
values.remove(getString(R.string.pref_theme_system_value));
|
||||||
|
theme.setEntryValues(values.toArray(new CharSequence[0]));
|
||||||
}
|
}
|
||||||
if (IS_DEBUG_BUILD) {
|
if (IS_DEBUG_BUILD) {
|
||||||
findPreference("pref_key_explode").setOnPreferenceClickListener(
|
findPreference("pref_key_explode").setOnPreferenceClickListener(
|
||||||
@@ -489,7 +495,13 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||||
.putExtra(EXTRA_APP_PACKAGE, packageName)
|
.putExtra(EXTRA_APP_PACKAGE, packageName)
|
||||||
.putExtra(EXTRA_CHANNEL_ID, channelId);
|
.putExtra(EXTRA_CHANNEL_ID, channelId);
|
||||||
startActivity(intent);
|
Context ctx = requireContext();
|
||||||
|
if (intent.resolveActivity(ctx.getPackageManager()) != null) {
|
||||||
|
startActivity(intent);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -511,7 +523,12 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
else uri = Uri.parse(ringtoneUri);
|
else uri = Uri.parse(ringtoneUri);
|
||||||
i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri);
|
i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri);
|
||||||
}
|
}
|
||||||
startActivityForResult(i, REQUEST_RINGTONE);
|
if (i.resolveActivity(requireActivity().getPackageManager()) != null) {
|
||||||
|
startActivityForResult(i, REQUEST_RINGTONE);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
|
||||||
|
LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,7 +663,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
} else {
|
} else {
|
||||||
// The user chose a ringtone other than the default
|
// The user chose a ringtone other than the default
|
||||||
Ringtone r = RingtoneManager.getRingtone(getContext(), uri);
|
Ringtone r = RingtoneManager.getRingtone(getContext(), uri);
|
||||||
if (r == null) {
|
if (r == null || "file".equals(uri.getScheme())) {
|
||||||
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
|
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
|
||||||
LENGTH_SHORT).show();
|
LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -64,12 +64,6 @@ public abstract class BaseMessageFragment extends BaseFragment
|
|||||||
@StringRes
|
@StringRes
|
||||||
protected abstract int getHintText();
|
protected abstract int getHintText();
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
message.showSoftKeyboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDa
|
|||||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.android.view.KeyboardAwareLinearLayout;
|
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
@@ -284,14 +283,10 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
|||||||
scrollToItemAtTop(item);
|
scrollToItemAtTop(item);
|
||||||
} else {
|
} else {
|
||||||
// wait with scrolling until keyboard opened
|
// wait with scrolling until keyboard opened
|
||||||
textInput.addOnKeyboardShownListener(
|
textInput.setOnKeyboardShownListener(() -> {
|
||||||
new KeyboardAwareLinearLayout.OnKeyboardShownListener() {
|
scrollToItemAtTop(item);
|
||||||
@Override
|
textInput.setOnKeyboardShownListener(null);
|
||||||
public void onKeyboardShown() {
|
});
|
||||||
scrollToItemAtTop(item);
|
|
||||||
textInput.removeOnKeyboardShownListener(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +327,6 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
|||||||
private void updateTextInput() {
|
private void updateTextInput() {
|
||||||
if (replyId != null) {
|
if (replyId != null) {
|
||||||
textInput.setHint(R.string.forum_message_reply_hint);
|
textInput.setHint(R.string.forum_message_reply_hint);
|
||||||
textInput.requestFocus();
|
|
||||||
textInput.showSoftKeyboard();
|
textInput.showSoftKeyboard();
|
||||||
} else {
|
} else {
|
||||||
textInput.setHint(R.string.forum_new_message_hint);
|
textInput.setHint(R.string.forum_new_message_hint);
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ public abstract class BriarAdapter<T, V extends ViewHolder>
|
|||||||
this.items.addAll(items);
|
this.items.addAll(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void replaceAll(Collection<T> items) {
|
||||||
|
this.items.replaceAll(items);
|
||||||
|
}
|
||||||
|
|
||||||
public void setItems(Collection<T> items) {
|
public void setItems(Collection<T> items) {
|
||||||
this.items.beginBatchedUpdates();
|
this.items.beginBatchedUpdates();
|
||||||
this.items.clear();
|
this.items.clear();
|
||||||
|
|||||||
@@ -4,13 +4,18 @@ import android.support.annotation.ColorRes;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.design.widget.Snackbar.Callback;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
||||||
import static android.support.v4.content.ContextCompat.getColor;
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
|
import static android.view.View.INVISIBLE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class BriarSnackbarBuilder {
|
public class BriarSnackbarBuilder {
|
||||||
@@ -30,6 +35,24 @@ public class BriarSnackbarBuilder {
|
|||||||
R.color.briar_button_text_positive));
|
R.color.briar_button_text_positive));
|
||||||
s.setAction(actionResId, onClickListener);
|
s.setAction(actionResId, onClickListener);
|
||||||
}
|
}
|
||||||
|
// Workaround for https://issuetracker.google.com/issues/64285517
|
||||||
|
if (duration == LENGTH_INDEFINITE && SDK_INT < 21) {
|
||||||
|
// Hide snackbar while it's opening to make bouncing less noticeable
|
||||||
|
s.getView().setVisibility(INVISIBLE);
|
||||||
|
s.addCallback(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onShown(Snackbar snackbar) {
|
||||||
|
snackbar.getView().setVisibility(VISIBLE);
|
||||||
|
// Request layout again in case snackbar is in wrong place
|
||||||
|
snackbar.getView().requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismissed(Snackbar snackbar, int event) {
|
||||||
|
snackbar.getView().setVisibility(INVISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,9 +92,11 @@ public class UiUtils {
|
|||||||
public static final float GREY_OUT = 0.5f;
|
public static final float GREY_OUT = 0.5f;
|
||||||
|
|
||||||
public static void showSoftKeyboard(View view) {
|
public static void showSoftKeyboard(View view) {
|
||||||
InputMethodManager imm = requireNonNull(
|
if (view.requestFocus()) {
|
||||||
getSystemService(view.getContext(), InputMethodManager.class));
|
InputMethodManager imm = requireNonNull(getSystemService(
|
||||||
imm.showSoftInput(view, SHOW_IMPLICIT);
|
view.getContext(), InputMethodManager.class));
|
||||||
|
imm.showSoftInput(view, SHOW_IMPLICIT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideSoftKeyboard(View view) {
|
public static void hideSoftKeyboard(View view) {
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
package org.briarproject.briar.android.view;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.design.widget.CoordinatorLayout;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
public class BriarRecyclerViewBehavior
|
|
||||||
extends CoordinatorLayout.Behavior<BriarRecyclerView> {
|
|
||||||
|
|
||||||
public BriarRecyclerViewBehavior(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onDependentViewChanged(CoordinatorLayout parent,
|
|
||||||
BriarRecyclerView child, View dependency) {
|
|
||||||
|
|
||||||
// FIXME the below code works, but does not reset margin when snackbar is dismissed
|
|
||||||
/*
|
|
||||||
int margin = 0;
|
|
||||||
if (dependency.isShown()) margin = dependency.getHeight();
|
|
||||||
|
|
||||||
// set snackbar height as bottom margin if it is shown
|
|
||||||
CoordinatorLayout.LayoutParams params =
|
|
||||||
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
|
|
||||||
params.setMargins(0, 0, 0, margin);
|
|
||||||
child.setLayoutParams(params);
|
|
||||||
|
|
||||||
child.scrollToPosition(0);
|
|
||||||
*/
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean layoutDependsOn(CoordinatorLayout parent,
|
|
||||||
BriarRecyclerView child, View dependency) {
|
|
||||||
// we only want to trigger the change
|
|
||||||
// only when the changes is from a snackbar
|
|
||||||
return dependency instanceof Snackbar.SnackbarLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ import android.widget.ProgressBar;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
public class CompositeSendButton extends FrameLayout {
|
public class CompositeSendButton extends FrameLayout {
|
||||||
@@ -75,33 +74,24 @@ public class CompositeSendButton extends FrameLayout {
|
|||||||
if (showImageButton) {
|
if (showImageButton) {
|
||||||
imageButton.setVisibility(VISIBLE);
|
imageButton.setVisibility(VISIBLE);
|
||||||
sendButton.setEnabled(false);
|
sendButton.setEnabled(false);
|
||||||
if (SDK_INT <= 15) {
|
sendButton.clearAnimation();
|
||||||
|
sendButton.animate().alpha(0f).withEndAction(() -> {
|
||||||
sendButton.setVisibility(INVISIBLE);
|
sendButton.setVisibility(INVISIBLE);
|
||||||
imageButton.setEnabled(true);
|
imageButton.setEnabled(true);
|
||||||
} else {
|
}).start();
|
||||||
sendButton.clearAnimation();
|
imageButton.clearAnimation();
|
||||||
sendButton.animate().alpha(0f).withEndAction(() -> {
|
imageButton.animate().alpha(1f).start();
|
||||||
sendButton.setVisibility(INVISIBLE);
|
|
||||||
imageButton.setEnabled(true);
|
|
||||||
}).start();
|
|
||||||
imageButton.clearAnimation();
|
|
||||||
imageButton.animate().alpha(1f).start();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
sendButton.setVisibility(VISIBLE);
|
sendButton.setVisibility(VISIBLE);
|
||||||
// enable/disable buttons right away to allow fast sending
|
// enable/disable buttons right away to allow fast sending
|
||||||
sendButton.setEnabled(sendEnabled);
|
sendButton.setEnabled(sendEnabled);
|
||||||
imageButton.setEnabled(false);
|
imageButton.setEnabled(false);
|
||||||
if (SDK_INT <= 15) {
|
sendButton.clearAnimation();
|
||||||
imageButton.setVisibility(INVISIBLE);
|
sendButton.animate().alpha(1f).start();
|
||||||
} else {
|
imageButton.clearAnimation();
|
||||||
sendButton.clearAnimation();
|
imageButton.animate().alpha(0f).withEndAction(() ->
|
||||||
sendButton.animate().alpha(1f).start();
|
imageButton.setVisibility(INVISIBLE)
|
||||||
imageButton.clearAnimation();
|
).start();
|
||||||
imageButton.animate().alpha(0f).withEndAction(() ->
|
|
||||||
imageButton.setVisibility(INVISIBLE)
|
|
||||||
).start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import android.util.AttributeSet;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import com.vanniktech.emoji.EmojiEditText;
|
|
||||||
import com.vanniktech.emoji.EmojiPopup;
|
import com.vanniktech.emoji.EmojiPopup;
|
||||||
import com.vanniktech.emoji.RecentEmoji;
|
import com.vanniktech.emoji.RecentEmoji;
|
||||||
|
|
||||||
@@ -28,10 +28,12 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
|||||||
import static android.view.KeyEvent.KEYCODE_ENTER;
|
import static android.view.KeyEvent.KEYCODE_ENTER;
|
||||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_SEND;
|
import static android.view.inputmethod.EditorInfo.IME_ACTION_SEND;
|
||||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||||
|
import static java.lang.Character.isWhitespace;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||||
|
|
||||||
public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
public class EmojiTextInputView extends LinearLayout implements
|
||||||
TextWatcher {
|
TextWatcher {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -40,12 +42,16 @@ public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
|||||||
private final AppCompatImageButton emojiToggle;
|
private final AppCompatImageButton emojiToggle;
|
||||||
private final EmojiPopup emojiPopup;
|
private final EmojiPopup emojiPopup;
|
||||||
private final EditText editText;
|
private final EditText editText;
|
||||||
|
private final InputMethodManager imm;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private TextInputListener listener;
|
private TextInputListener listener;
|
||||||
|
@Nullable
|
||||||
|
private OnKeyboardShownListener keyboardShownListener;
|
||||||
private int maxLength = Integer.MAX_VALUE;
|
private int maxLength = Integer.MAX_VALUE;
|
||||||
private boolean emptyTextAllowed = false;
|
private boolean emptyTextAllowed = false;
|
||||||
private boolean isEmpty = true;
|
private boolean isEmpty = true;
|
||||||
|
private boolean keyboardOpen = false;
|
||||||
|
|
||||||
public EmojiTextInputView(Context context) {
|
public EmojiTextInputView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -79,7 +85,6 @@ public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
|||||||
editText = findViewById(R.id.input_text);
|
editText = findViewById(R.id.input_text);
|
||||||
editText.setPadding(0, 0, paddingEnd, paddingBottom);
|
editText.setPadding(0, 0, paddingEnd, paddingBottom);
|
||||||
if (maxLines > 0) editText.setMaxLines(maxLines);
|
if (maxLines > 0) editText.setMaxLines(maxLines);
|
||||||
editText.setOnClickListener(v -> showSoftKeyboard());
|
|
||||||
editText.addTextChangedListener(this);
|
editText.addTextChangedListener(this);
|
||||||
editText.setOnEditorActionListener((v, actionId, event) -> {
|
editText.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
if (actionId == IME_ACTION_SEND) {
|
if (actionId == IME_ACTION_SEND) {
|
||||||
@@ -103,18 +108,30 @@ public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
|||||||
// stuff we can't do in edit mode goes below
|
// stuff we can't do in edit mode goes below
|
||||||
if (isInEditMode()) {
|
if (isInEditMode()) {
|
||||||
emojiPopup = null;
|
emojiPopup = null;
|
||||||
|
imm = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||||
|
imm = (InputMethodManager) requireNonNull(o);
|
||||||
|
|
||||||
BriarApplication app =
|
BriarApplication app =
|
||||||
(BriarApplication) context.getApplicationContext();
|
(BriarApplication) context.getApplicationContext();
|
||||||
app.getApplicationComponent().inject(this);
|
app.getApplicationComponent().inject(this);
|
||||||
emojiPopup = EmojiPopup.Builder
|
emojiPopup = EmojiPopup.Builder
|
||||||
.fromRootView(this)
|
.fromRootView(getRootView())
|
||||||
.setRecentEmoji(recentEmoji)
|
.setRecentEmoji(recentEmoji)
|
||||||
.setOnEmojiPopupShownListener(this::showKeyboardIcon)
|
.setOnEmojiPopupShownListener(this::showKeyboardIcon)
|
||||||
.setOnEmojiPopupDismissListener(this::showEmojiIcon)
|
.setOnEmojiPopupDismissListener(this::showEmojiIcon)
|
||||||
.build((EmojiEditText) editText);
|
.setKeyboardAnimationStyle(R.style.emoji_fade_animation_style)
|
||||||
|
.setOnSoftKeyboardOpenListener(this::onKeyboardOpened)
|
||||||
|
.setOnSoftKeyboardCloseListener(this::onKeyboardClosed)
|
||||||
|
.setIconColor(resolveColorAttribute(getContext(),
|
||||||
|
R.attr.colorControlNormal))
|
||||||
|
.build(editText);
|
||||||
emojiToggle.setOnClickListener(v -> emojiPopup.toggle());
|
emojiToggle.setOnClickListener(v -> emojiPopup.toggle());
|
||||||
|
editText.setOnClickListener(v -> {
|
||||||
|
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -125,19 +142,31 @@ public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
|||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before,
|
public void onTextChanged(CharSequence s, int start, int before,
|
||||||
int count) {
|
int count) {
|
||||||
// Need to start at position 0 to change empty
|
if (emptyTextAllowed || listener == null) return;
|
||||||
if (start != 0 || emptyTextAllowed || listener == null) return;
|
// Work out whether the trimmed text has become empty or non-empty
|
||||||
if (s.length() == 0) {
|
if (isEmpty) {
|
||||||
if (!isEmpty) {
|
// We only need to check the characters that were added
|
||||||
|
if (countLeadingWhitespace(s, start, count) < count) {
|
||||||
|
isEmpty = false;
|
||||||
|
listener.onTextIsEmptyChanged(false);
|
||||||
|
}
|
||||||
|
} else if (before > 0) {
|
||||||
|
// Characters have been removed or replaced - check from the start
|
||||||
|
int length = s.length();
|
||||||
|
if (countLeadingWhitespace(s, 0, length) == length) {
|
||||||
isEmpty = true;
|
isEmpty = true;
|
||||||
listener.onTextIsEmptyChanged(true);
|
listener.onTextIsEmptyChanged(true);
|
||||||
}
|
}
|
||||||
} else if (isEmpty) {
|
|
||||||
isEmpty = false;
|
|
||||||
listener.onTextIsEmptyChanged(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int countLeadingWhitespace(CharSequence s, int off, int len) {
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (!isWhitespace(s.charAt(off + i))) return i;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
}
|
}
|
||||||
@@ -218,6 +247,10 @@ public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
|||||||
editText.setHint(hint);
|
editText.setHint(hint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isKeyboardOpen() {
|
||||||
|
return keyboardOpen || imm.isFullscreenMode();
|
||||||
|
}
|
||||||
|
|
||||||
private void showEmojiIcon() {
|
private void showEmojiIcon() {
|
||||||
emojiToggle.setImageResource(R.drawable.ic_emoji_toggle);
|
emojiToggle.setImageResource(R.drawable.ic_emoji_toggle);
|
||||||
}
|
}
|
||||||
@@ -227,22 +260,43 @@ public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
void showSoftKeyboard() {
|
void showSoftKeyboard() {
|
||||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
if (editText.requestFocus()) imm.showSoftInput(editText, SHOW_IMPLICIT);
|
||||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
|
||||||
imm.showSoftInput(editText, SHOW_IMPLICIT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hideSoftKeyboard() {
|
void hideSoftKeyboard() {
|
||||||
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||||
IBinder token = editText.getWindowToken();
|
IBinder token = editText.getWindowToken();
|
||||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
|
||||||
imm.hideSoftInputFromWindow(token, 0);
|
imm.hideSoftInputFromWindow(token, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onKeyboardOpened(
|
||||||
|
@SuppressWarnings("unused") int keyboardHeight) {
|
||||||
|
keyboardOpen = true;
|
||||||
|
if (keyboardShownListener != null)
|
||||||
|
keyboardShownListener.onKeyboardShown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onKeyboardClosed() {
|
||||||
|
if (imm.isFullscreenMode()) {
|
||||||
|
onKeyboardOpened(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
keyboardOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOnKeyboardShownListener(
|
||||||
|
@Nullable OnKeyboardShownListener listener) {
|
||||||
|
keyboardShownListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
interface TextInputListener {
|
interface TextInputListener {
|
||||||
void onTextIsEmptyChanged(boolean isEmpty);
|
void onTextIsEmptyChanged(boolean isEmpty);
|
||||||
|
|
||||||
void onSendEvent();
|
void onSendEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface OnKeyboardShownListener {
|
||||||
|
void onKeyboardShown();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,225 +0,0 @@
|
|||||||
/*
|
|
||||||
Taken from Signal, licences under GPLv3
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.briarproject.briar.android.view;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.annotation.UiThread;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static android.content.Context.WINDOW_SERVICE;
|
|
||||||
import static android.view.Surface.ROTATION_270;
|
|
||||||
import static android.view.Surface.ROTATION_90;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RelativeLayout that, when a view container, will report back when it thinks
|
|
||||||
* a soft keyboard has been opened and what its height would be.
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
public class KeyboardAwareLinearLayout extends LinearLayout {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(KeyboardAwareLinearLayout.class.getName());
|
|
||||||
|
|
||||||
private final Rect rect = new Rect();
|
|
||||||
private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>();
|
|
||||||
private final int minKeyboardSize;
|
|
||||||
private final int minCustomKeyboardSize;
|
|
||||||
private final int defaultCustomKeyboardSize;
|
|
||||||
private final int minCustomKeyboardTopMargin;
|
|
||||||
private final int statusBarHeight;
|
|
||||||
|
|
||||||
private int viewInset;
|
|
||||||
|
|
||||||
private boolean keyboardOpen = false;
|
|
||||||
private int rotation = -1;
|
|
||||||
|
|
||||||
public KeyboardAwareLinearLayout(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyboardAwareLinearLayout(Context context,
|
|
||||||
@Nullable AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyboardAwareLinearLayout(Context context,
|
|
||||||
@Nullable AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
rotation = getDeviceRotation();
|
|
||||||
int statusBarRes = getResources()
|
|
||||||
.getIdentifier("status_bar_height", "dimen", "android");
|
|
||||||
minKeyboardSize =
|
|
||||||
getResources().getDimensionPixelSize(R.dimen.min_keyboard_size);
|
|
||||||
minCustomKeyboardSize = getResources()
|
|
||||||
.getDimensionPixelSize(R.dimen.min_custom_keyboard_size);
|
|
||||||
defaultCustomKeyboardSize = getResources()
|
|
||||||
.getDimensionPixelSize(R.dimen.default_custom_keyboard_size);
|
|
||||||
minCustomKeyboardTopMargin = getResources()
|
|
||||||
.getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin);
|
|
||||||
statusBarHeight = statusBarRes > 0 ?
|
|
||||||
getResources().getDimensionPixelSize(statusBarRes) : 0;
|
|
||||||
viewInset = getViewInset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
updateRotation();
|
|
||||||
updateKeyboardState();
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRotation() {
|
|
||||||
int oldRotation = rotation;
|
|
||||||
rotation = getDeviceRotation();
|
|
||||||
if (oldRotation != rotation) {
|
|
||||||
LOG.info("Rotation changed");
|
|
||||||
onKeyboardClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateKeyboardState() {
|
|
||||||
if (isLandscape()) {
|
|
||||||
if (keyboardOpen) onKeyboardClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (viewInset == 0 && Build.VERSION.SDK_INT >= 21)
|
|
||||||
viewInset = getViewInset();
|
|
||||||
int availableHeight =
|
|
||||||
getRootView().getHeight() - statusBarHeight - viewInset;
|
|
||||||
getWindowVisibleDisplayFrame(rect);
|
|
||||||
|
|
||||||
int keyboardHeight = availableHeight - (rect.bottom - rect.top);
|
|
||||||
|
|
||||||
if (keyboardHeight > minKeyboardSize) {
|
|
||||||
if (getKeyboardHeight() != keyboardHeight)
|
|
||||||
setKeyboardPortraitHeight(keyboardHeight);
|
|
||||||
if (!keyboardOpen) onKeyboardOpen(keyboardHeight);
|
|
||||||
} else if (keyboardOpen) {
|
|
||||||
onKeyboardClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(21)
|
|
||||||
private int getViewInset() {
|
|
||||||
try {
|
|
||||||
Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
|
|
||||||
attachInfoField.setAccessible(true);
|
|
||||||
Object attachInfo = attachInfoField.get(this);
|
|
||||||
if (attachInfo != null) {
|
|
||||||
Field stableInsetsField =
|
|
||||||
attachInfo.getClass().getDeclaredField("mStableInsets");
|
|
||||||
stableInsetsField.setAccessible(true);
|
|
||||||
Rect insets = (Rect) stableInsetsField.get(attachInfo);
|
|
||||||
return insets.bottom;
|
|
||||||
}
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
LOG.log(WARNING,
|
|
||||||
"field reflection error when measuring view inset", e);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
LOG.log(WARNING,
|
|
||||||
"access reflection error when measuring view inset", e);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onKeyboardOpen(int keyboardHeight) {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("onKeyboardOpen(" + keyboardHeight + ")");
|
|
||||||
keyboardOpen = true;
|
|
||||||
|
|
||||||
notifyShownListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onKeyboardClose() {
|
|
||||||
LOG.info("onKeyboardClose()");
|
|
||||||
keyboardOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isKeyboardOpen() {
|
|
||||||
return keyboardOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getKeyboardHeight() {
|
|
||||||
return isLandscape() ? getKeyboardLandscapeHeight() :
|
|
||||||
getKeyboardPortraitHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLandscape() {
|
|
||||||
int rotation = getDeviceRotation();
|
|
||||||
return rotation == ROTATION_90 || rotation == ROTATION_270;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getDeviceRotation() {
|
|
||||||
WindowManager windowManager =
|
|
||||||
(WindowManager) getContext().getSystemService(WINDOW_SERVICE);
|
|
||||||
return requireNonNull(windowManager).getDefaultDisplay().getRotation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getKeyboardLandscapeHeight() {
|
|
||||||
return Math.max(getHeight(), getRootView().getHeight()) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getKeyboardPortraitHeight() {
|
|
||||||
SharedPreferences prefs =
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(getContext());
|
|
||||||
int keyboardHeight = prefs.getInt("keyboard_height_portrait",
|
|
||||||
defaultCustomKeyboardSize);
|
|
||||||
return clamp(keyboardHeight, minCustomKeyboardSize,
|
|
||||||
getRootView().getHeight() - minCustomKeyboardTopMargin);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int clamp(int value, int min, int max) {
|
|
||||||
return Math.min(Math.max(value, min), max);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setKeyboardPortraitHeight(int height) {
|
|
||||||
SharedPreferences prefs =
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(getContext());
|
|
||||||
prefs.edit().putInt("keyboard_height_portrait", height).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addOnKeyboardShownListener(OnKeyboardShownListener listener) {
|
|
||||||
shownListeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeOnKeyboardShownListener(
|
|
||||||
OnKeyboardShownListener listener) {
|
|
||||||
shownListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyShownListeners() {
|
|
||||||
// Make a copy as listeners may remove themselves when called
|
|
||||||
Set<OnKeyboardShownListener> listeners = new HashSet<>(shownListeners);
|
|
||||||
for (OnKeyboardShownListener listener : listeners) {
|
|
||||||
listener.onKeyboardShown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnKeyboardShownListener {
|
|
||||||
void onKeyboardShown();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.design.widget.CoordinatorLayout;
|
||||||
|
import android.support.design.widget.CoordinatorLayout.Behavior;
|
||||||
|
import android.support.design.widget.CoordinatorLayout.LayoutParams;
|
||||||
|
import android.support.design.widget.Snackbar.SnackbarLayout;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This behavior makes room for a snackbar at the bottom of the screen. The
|
||||||
|
* proper solution is to use layout_dodgeInsetEdges="bottom", but when used on
|
||||||
|
* a scrollable view that results in the view being pushed under the app bar
|
||||||
|
* (see https://issuetracker.google.com/issues/116541304).
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
public class SnackbarAwareBehavior<V extends View> extends Behavior<V> {
|
||||||
|
|
||||||
|
public SnackbarAwareBehavior(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDependentViewChanged(CoordinatorLayout parent,
|
||||||
|
V child, View snackbar) {
|
||||||
|
setMargin(child, snackbar.getHeight());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDependentViewRemoved(CoordinatorLayout parent,
|
||||||
|
V child, View snackbar) {
|
||||||
|
setMargin(child, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean layoutDependsOn(CoordinatorLayout parent,
|
||||||
|
V child, View dependency) {
|
||||||
|
return dependency instanceof SnackbarLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMargin(V child, int margin) {
|
||||||
|
LayoutParams params = (LayoutParams) child.getLayoutParams();
|
||||||
|
params.setMargins(0, 0, 0, margin);
|
||||||
|
child.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import android.widget.LinearLayout;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.view.KeyboardAwareLinearLayout.OnKeyboardShownListener;
|
import org.briarproject.briar.android.view.EmojiTextInputView.OnKeyboardShownListener;
|
||||||
|
|
||||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
@@ -139,13 +139,9 @@ public class TextInputView extends LinearLayout {
|
|||||||
textInput.hideSoftKeyboard();
|
textInput.hideSoftKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addOnKeyboardShownListener(OnKeyboardShownListener listener) {
|
public void setOnKeyboardShownListener(
|
||||||
textInput.addOnKeyboardShownListener(listener);
|
@Nullable OnKeyboardShownListener listener) {
|
||||||
}
|
textInput.setOnKeyboardShownListener(listener);
|
||||||
|
|
||||||
public void removeOnKeyboardShownListener(
|
|
||||||
OnKeyboardShownListener listener) {
|
|
||||||
textInput.removeOnKeyboardShownListener(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,17 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:passwordToggleEnabled="true">
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
<EditText
|
<android.support.design.widget.TextInputEditText
|
||||||
android:id="@+id/current_password_entry"
|
android:id="@+id/current_password_entry"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/current_password"
|
android:hint="@string/current_password"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
|
|
||||||
|
<requestFocus/>
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<android.support.design.widget.TextInputLayout
|
||||||
@@ -49,11 +53,12 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/current_password_entry_wrapper"
|
app:layout_constraintTop_toBottomOf="@id/current_password_entry_wrapper"
|
||||||
app:passwordToggleEnabled="true">
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
<EditText
|
<android.support.design.widget.TextInputEditText
|
||||||
android:id="@+id/new_password_entry"
|
android:id="@+id/new_password_entry"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/choose_new_password"
|
android:hint="@string/choose_new_password"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
@@ -69,12 +74,13 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/new_password_entry_wrapper"
|
app:layout_constraintTop_toBottomOf="@id/new_password_entry_wrapper"
|
||||||
app:passwordToggleEnabled="true">
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
<EditText
|
<android.support.design.widget.TextInputEditText
|
||||||
android:id="@+id/new_password_confirm"
|
android:id="@+id/new_password_confirm"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/confirm_new_password"
|
android:hint="@string/confirm_new_password"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|||||||
@@ -15,14 +15,17 @@
|
|||||||
app:errorEnabled="true"
|
app:errorEnabled="true"
|
||||||
app:hintEnabled="false">
|
app:hintEnabled="false">
|
||||||
|
|
||||||
<EditText
|
<android.support.design.widget.TextInputEditText
|
||||||
android:id="@+id/createForumNameEntry"
|
android:id="@+id/createForumNameEntry"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/choose_forum_hint"
|
android:hint="@string/choose_forum_hint"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="text|textCapSentences"
|
android:inputType="text|textCapSentences"
|
||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
|
|
||||||
|
<requestFocus/>
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
app:cardCornerRadius="0dp"
|
app:cardCornerRadius="0dp"
|
||||||
app:cardUseCompatPadding="false">
|
app:cardUseCompatPadding="false">
|
||||||
|
|
||||||
<EditText
|
<android.support.design.widget.TextInputEditText
|
||||||
android:id="@+id/urlInput"
|
android:id="@+id/urlInput"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -26,9 +26,14 @@
|
|||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:hint="@string/blogs_rss_feeds_import_hint"
|
android:hint="@string/blogs_rss_feeds_import_hint"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="textUri"
|
android:inputType="textUri"
|
||||||
android:padding="@dimen/margin_medium"
|
android:padding="@dimen/margin_medium"
|
||||||
android:textColor="?android:attr/textColorPrimary"/>
|
android:textColor="?android:attr/textColorPrimary">
|
||||||
|
|
||||||
|
<requestFocus/>
|
||||||
|
|
||||||
|
</android.support.design.widget.TextInputEditText>
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,11 @@
|
|||||||
android:gravity="bottom"
|
android:gravity="bottom"
|
||||||
app:buttonText="@string/blogs_publish_blog_post"
|
app:buttonText="@string/blogs_publish_blog_post"
|
||||||
app:fillHeight="true"
|
app:fillHeight="true"
|
||||||
app:hint="@string/blogs_write_blog_post_body_hint"/>
|
app:hint="@string/blogs_write_blog_post_body_hint">
|
||||||
|
|
||||||
|
<requestFocus/>
|
||||||
|
|
||||||
|
</org.briarproject.briar.android.view.LargeTextInputView>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
tools:parentTag="org.briarproject.briar.android.view.KeyboardAwareLinearLayout"
|
tools:parentTag="android.widget.LinearLayout"
|
||||||
tools:showIn="@layout/fragment_reblog">
|
tools:showIn="@layout/fragment_reblog">
|
||||||
|
|
||||||
<android.support.v7.widget.AppCompatImageButton
|
<android.support.v7.widget.AppCompatImageButton
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:hint="@string/set_contact_alias_hint"
|
android:hint="@string/set_contact_alias_hint"
|
||||||
android:inputType="textPersonName"
|
android:inputType="text|textCapWords"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:textSize="@dimen/text_size_medium"/>
|
android:textSize="@dimen/text_size_medium"/>
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
android:id="@+id/list"
|
android:id="@+id/list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="org.briarproject.briar.android.view.SnackbarAwareBehavior"
|
||||||
app:scrollToEnd="false"/>
|
app:scrollToEnd="false"/>
|
||||||
|
|
||||||
<io.github.kobakei.materialfabspeeddial.FabSpeedDial
|
<io.github.kobakei.materialfabspeeddial.FabSpeedDial
|
||||||
@@ -19,6 +20,8 @@
|
|||||||
app:fab_fabRippleColor="@android:color/transparent"
|
app:fab_fabRippleColor="@android:color/transparent"
|
||||||
app:fab_menu="@menu/contact_list_actions"
|
app:fab_menu="@menu/contact_list_actions"
|
||||||
app:fab_miniFabTextBackground="@color/briar_accent"
|
app:fab_miniFabTextBackground="@color/briar_accent"
|
||||||
app:fab_miniFabTextColor="@android:color/white"/>
|
app:fab_miniFabTextColor="@android:color/white"
|
||||||
|
app:layout_anchorGravity="bottom|right|end"
|
||||||
|
app:layout_behavior="org.briarproject.briar.android.view.SnackbarAwareBehavior"/>
|
||||||
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</android.support.design.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -15,14 +15,17 @@
|
|||||||
app:errorEnabled="true"
|
app:errorEnabled="true"
|
||||||
app:hintEnabled="false">
|
app:hintEnabled="false">
|
||||||
|
|
||||||
<EditText
|
<android.support.design.widget.TextInputEditText
|
||||||
android:id="@+id/name"
|
android:id="@+id/name"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/groups_create_group_hint"
|
android:hint="@string/groups_create_group_hint"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="text|textCapSentences"
|
android:inputType="text|textCapSentences"
|
||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
|
|
||||||
|
<requestFocus/>
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_bias="0.0"/>
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/stepOneText"
|
android:id="@+id/stepOneText"
|
||||||
@@ -74,12 +75,14 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/imageView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:layout_marginTop="32dp"
|
android:layout_marginTop="32dp"
|
||||||
android:src="@drawable/ic_nickname"
|
android:src="@drawable/ic_nickname"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/nicknameIcon"
|
app:layout_constraintBottom_toTopOf="@+id/nicknameIcon"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_max="256dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stepOneText"
|
app:layout_constraintTop_toBottomOf="@+id/stepOneText"
|
||||||
tools:ignore="ContentDescription"/>
|
tools:ignore="ContentDescription"/>
|
||||||
@@ -118,6 +121,7 @@
|
|||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
app:errorEnabled="true"
|
app:errorEnabled="true"
|
||||||
app:hintEnabled="false"
|
app:hintEnabled="false"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/space"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -133,6 +137,16 @@
|
|||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/space"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/addButton"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_default="wrap"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/addButton"
|
android:id="@+id/addButton"
|
||||||
style="@style/BriarButton"
|
style="@style/BriarButton"
|
||||||
|
|||||||
@@ -31,6 +31,9 @@
|
|||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
|
|
||||||
|
<requestFocus/>
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
|
|
||||||
<requestFocus/>
|
<requestFocus/>
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
android:maxLines="1">
|
android:maxLines="1">
|
||||||
|
|
||||||
<requestFocus/>
|
<requestFocus/>
|
||||||
|
|
||||||
</android.support.design.widget.TextInputEditText>
|
</android.support.design.widget.TextInputEditText>
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|||||||
@@ -16,6 +16,11 @@
|
|||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_delete_all_messages"
|
||||||
|
android:title="@string/delete_all_messages"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_social_remove_person"
|
android:id="@+id/action_social_remove_person"
|
||||||
android:icon="@drawable/action_delete_white"
|
android:icon="@drawable/action_delete_white"
|
||||||
|
|||||||
@@ -175,6 +175,13 @@
|
|||||||
<string name="step_1">واحدة</string>
|
<string name="step_1">واحدة</string>
|
||||||
<!--This is a numeral indicating the second step in a series of screens-->
|
<!--This is a numeral indicating the second step in a series of screens-->
|
||||||
<string name="step_2">2</string>
|
<string name="step_2">2</string>
|
||||||
|
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||||
|
<!--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-->
|
||||||
|
<!--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-->
|
||||||
<!--Introductions-->
|
<!--Introductions-->
|
||||||
<string name="introduction_onboarding_title">قم بتقديم جهات إتصالك</string>
|
<string name="introduction_onboarding_title">قم بتقديم جهات إتصالك</string>
|
||||||
<string name="introduction_onboarding_text">يمكنك أن تقدم جهات إتصالك لبعضها البعض، فلا يحتاجون للمقابلة الشخصية ليتواصلوا عبر Briar (براير).</string>
|
<string name="introduction_onboarding_text">يمكنك أن تقدم جهات إتصالك لبعضها البعض، فلا يحتاجون للمقابلة الشخصية ليتواصلوا عبر Briar (براير).</string>
|
||||||
|
|||||||
@@ -118,7 +118,9 @@
|
|||||||
<string name="message_hint">Mesaj yazın</string>
|
<string name="message_hint">Mesaj yazın</string>
|
||||||
<string name="image_caption_hint">Mövzu əlavə edin (isteyə bağlı)</string>
|
<string name="image_caption_hint">Mövzu əlavə edin (isteyə bağlı)</string>
|
||||||
<string name="image_attach">Şəkil əlavə edin</string>
|
<string name="image_attach">Şəkil əlavə edin</string>
|
||||||
<string name="image_attach_error">Şəkil əlavə edilmədi</string>
|
<string name="image_attach_error">Şəkil (lər) əlavə etmək alınmadı</string>
|
||||||
|
<string name="image_attach_error_too_big">Şəkilin həcmi böyükdür. Limit %d MB.</string>
|
||||||
|
<string name="image_attach_error_invalid_mime_type">Şəkil formatı dəstəklənmir: %s</string>
|
||||||
<string name="set_contact_alias">Kontakt adı dəyişdirin</string>
|
<string name="set_contact_alias">Kontakt adı dəyişdirin</string>
|
||||||
<string name="set_contact_alias_hint">Əlaqə adı</string>
|
<string name="set_contact_alias_hint">Əlaqə adı</string>
|
||||||
<string name="set_alias_button">Dəyiş</string>
|
<string name="set_alias_button">Dəyiş</string>
|
||||||
@@ -138,6 +140,7 @@
|
|||||||
<string name="dialog_title_image_support">İndi bu kontakta şəkilləri göndərə bilərsiniz</string>
|
<string name="dialog_title_image_support">İndi bu kontakta şəkilləri göndərə bilərsiniz</string>
|
||||||
<string name="dialog_message_image_support">Şəkilləri əlavə etmək üçün bu simvola toxunun.</string>
|
<string name="dialog_message_image_support">Şəkilləri əlavə etmək üçün bu simvola toxunun.</string>
|
||||||
<!--Adding Contacts-->
|
<!--Adding Contacts-->
|
||||||
|
<string name="add_contact_title">Yaxında kontakt əlavə etmək</string>
|
||||||
<string name="face_to_face">Kontakta əlavə etmək istədiyiniz şəxslə tanış olmalısınız. Bu, hər kəsin kimliyinizi və ya mesajlarınızı gələcəkdə oxumasını maneə törədir.</string>
|
<string name="face_to_face">Kontakta əlavə etmək istədiyiniz şəxslə tanış olmalısınız. Bu, hər kəsin kimliyinizi və ya mesajlarınızı gələcəkdə oxumasını maneə törədir.</string>
|
||||||
<string name="continue_button">Davam et</string>
|
<string name="continue_button">Davam et</string>
|
||||||
<string name="try_again_button">Yenidən cəhd elə</string>
|
<string name="try_again_button">Yenidən cəhd elə</string>
|
||||||
@@ -155,12 +158,56 @@
|
|||||||
<string name="connection_error_explanation">Həmin Wi-Fi şəbəkəsinə qoşulduğunuzu yoxlayın.</string>
|
<string name="connection_error_explanation">Həmin Wi-Fi şəbəkəsinə qoşulduğunuzu yoxlayın.</string>
|
||||||
<string name="connection_error_feedback">Bu problem davam edərsə, tətbiqin təkmilləşdirilməsinə kömək etmək üçün rəy göndərin.</string>
|
<string name="connection_error_feedback">Bu problem davam edərsə, tətbiqin təkmilləşdirilməsinə kömək etmək üçün rəy göndərin.</string>
|
||||||
<!--Adding Contacts Remotely-->
|
<!--Adding Contacts Remotely-->
|
||||||
|
<string name="add_contact_remotely_title_case">Məsafədə kontakt əlavə etmək </string>
|
||||||
|
<string name="add_contact_nearby_title">Yaxında kontakt əlavə etmək </string>
|
||||||
|
<string name="add_contact_remotely_title">Məsafədə kontakt əlavə etmək </string>
|
||||||
|
<string name="contact_name_hint">Niklə kontakt saxlayın</string>
|
||||||
|
<string name="contact_link_intro">Kontaktın linkini buraya daxil edin</string>
|
||||||
|
<string name="contact_link_hint">Kontaktın linki</string>
|
||||||
|
<string name="paste_button">Yapışdır</string>
|
||||||
|
<string name="add_contact_button">Kontankt əlavə et</string>
|
||||||
<string name="copy_button">Kopyala</string>
|
<string name="copy_button">Kopyala</string>
|
||||||
<string name="share_button">Paylaş</string>
|
<string name="share_button">Paylaş</string>
|
||||||
|
<string name="send_link_title">Link mübadiləsi</string>
|
||||||
|
<string name="add_contact_choose_nickname">Ad seç</string>
|
||||||
|
<string name="add_contact_choose_a_nickname">Ad daxil et</string>
|
||||||
|
<string name="nickname_intro">Niklə kontakt saxlayın. Bunu ancaq siz görəcəksiniz. </string>
|
||||||
|
<string name="your_link">Əlavə etmək istədiyiniz kontakt üçün bu linki verin</string>
|
||||||
|
<string name="link_clip_label">Briar link</string>
|
||||||
|
<string name="link_copied_toast">Link kopyalandı</string>
|
||||||
|
<string name="adding_contact_error">Kontaktı əlavə edərkən səhv baş verdi.</string>
|
||||||
|
<string name="pending_contact_requests_snackbar">Gözləyən kontakt sorğuları var. </string>
|
||||||
|
<string name="pending_contact_requests">Kənara qoyulmuş kontak sorğuları</string>
|
||||||
|
<string name="no_pending_contacts">Gözləyən kontakt yoxdur</string>
|
||||||
<string name="add_contact_remote_connecting">Qoşulur...</string>
|
<string name="add_contact_remote_connecting">Qoşulur...</string>
|
||||||
|
<string name="waiting_for_contact_to_come_online">Kontaktı xəttdə gözləyin ...</string>
|
||||||
<string name="connecting">Qoşulur...</string>
|
<string name="connecting">Qoşulur...</string>
|
||||||
|
<string name="adding_contact">Kontaktın əlavə etməsi... </string>
|
||||||
|
<string name="adding_contact_failed">Kontakt əlavə etməsi alınmadı</string>
|
||||||
|
<string name="dialog_title_remove_pending_contact">Silməyi təstiqlə</string>
|
||||||
|
<string name="dialog_message_remove_pending_contact">Bu kontakt hələ də əlavə olunur. İndi onu çıxararsanız, əlavə olunmayacaq.</string>
|
||||||
|
<string name="own_link_error">Kontaktınızın linkini daxil edin, özünüzü deyil</string>
|
||||||
|
<string name="nickname_missing">Zəhmət olmasa adı daxil edin</string>
|
||||||
|
<string name="invalid_link">Səhv link</string>
|
||||||
|
<string name="unsupported_link">Bu link Briar-ın daha yeni versiyasından gəlir. Ən son versiyaya keçin və yenidən cəhd edin.</string>
|
||||||
|
<string name="intent_own_link">Öz linkini açdın. Əlavə etmək istədiyiniz kontaktlardan birini istifadə edin!</string>
|
||||||
|
<string name="missing_link">Zəhmət olmasa linki daxil edin</string>
|
||||||
<!--This is a numeral indicating the first step in a series of screens-->
|
<!--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-->
|
<!--This is a numeral indicating the second step in a series of screens-->
|
||||||
|
<string name="step_2">2</string>
|
||||||
|
<string name="offline_state">İnternet bağlantısı yoxdur</string>
|
||||||
|
<string name="duplicate_link_dialog_title">Dublikat Link</string>
|
||||||
|
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||||
|
<!--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">Eyni şəxs</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">Fərqli şəxs</string>
|
||||||
|
<string name="pending_contact_updated_toast">Gözləyən kontakt yeniləndi</string>
|
||||||
<!--Introductions-->
|
<!--Introductions-->
|
||||||
<string name="introduction_onboarding_title">Kontaktınızı tanıtın</string>
|
<string name="introduction_onboarding_title">Kontaktınızı tanıtın</string>
|
||||||
<string name="introduction_onboarding_text">Kontaktlarınızı bir-birinizə təqdim edə bilərsiniz, lakin Briar-a qoşulmaq üçün şəxsən görüşmək lazım deyil.</string>
|
<string name="introduction_onboarding_text">Kontaktlarınızı bir-birinizə təqdim edə bilərsiniz, lakin Briar-a qoşulmaq üçün şəxsən görüşmək lazım deyil.</string>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user