Compare commits

..

121 Commits

Author SHA1 Message Date
Michael Rogers
a9437f7985 Bumped version number for beta release. 2017-07-28 18:01:19 +01:00
akwizgran
8141a97fc9 Merge branch '1015-recent-emoji-crash' into 'master'
Prevent a crash caused by empty emoji

Closes #1015

See merge request !571
2017-07-28 16:59:02 +00:00
Torsten Grote
db842bd7e4 Prevent a crash caused by empty emoji
The crash happens because the serialization of recently used emoji uses
';' to separate the emojis.
One of the ASCII emojis however has a ';' in the beginning.
When this one is used by the user,
it causes an empty string to be returned when deserializing.

This commit prevents the crash by changing the separator to a tab.
It uses a different settings string to store the emoji,
so users will lose the list of recently used emoji when they update to
this version.

PS. That wasn't my idea ;)
2017-07-28 13:49:51 -03:00
Torsten Grote
6dbec3a864 Merge branch 'enable-logging-for-beta-builds' into 'master'
Enable logging for beta builds

See merge request !573
2017-07-28 15:58:01 +00:00
akwizgran
29f658cf4d Merge branch '1006-blog-crash' into 'master'
Prevent crash in blog by ensuring a listener always exists

Closes #1006

See merge request !574
2017-07-28 15:53:43 +00:00
akwizgran
ca83744a84 Merge branch 'close-feed-stream' into 'master'
Close InputStream from RSS feed and prevent NPE

See merge request !572
2017-07-28 15:48:01 +00:00
Torsten Grote
d91a9e2be4 Prevent crash in blog by ensuring a listener always exists 2017-07-28 12:42:56 -03:00
akwizgran
8408c3f467 Enable logging for beta builds.
Some devices were logging and others not, due to the log level being set in the SplashScreenActivity constructor.
2017-07-28 16:41:24 +01:00
Torsten Grote
544c83a64c Close InputStream from RSS feed and prevent NPE 2017-07-28 10:38:01 -03:00
Michael Rogers
3800cd5e4f Bumped version number for beta release. 2017-07-28 11:17:09 +01:00
akwizgran
259f2cd419 Merge branch '993-fix-full-text-blog-posts' into 'master'
Show blog posts with full text when clicked

Closes #993

See merge request !570
2017-07-26 11:01:38 +00:00
Torsten Grote
20eb022c36 Show blog posts with full text when clicked
This fixes a regression that was introduced in !551.
2017-07-25 15:50:04 -03:00
akwizgran
531e555b52 Bumped version number for beta release. 2017-07-25 18:43:19 +01:00
akwizgran
a9024aa34b Merge branch '955-shared-with-update' into 'master'
Fix "shared with" counter not being updated

Closes #955

See merge request !569
2017-07-25 17:40:40 +00:00
akwizgran
d4e3b7842c Merge branch 'blog-sharing-tests' into 'master'
Add unit tests for BlogSharingManager

See merge request !567
2017-07-25 17:40:29 +00:00
Torsten Grote
167fddfbcc Add unit tests for BlogSharingManager 2017-07-25 12:45:36 -03:00
Torsten Grote
a48d642648 Fix UI bug in CreateForumActivity and adapt group creation 2017-07-25 12:32:53 -03:00
Torsten Grote
9a70f054c7 Use proper GroupId when reacting to accepted invitations
Fixes #955
2017-07-25 10:03:13 -03:00
Torsten Grote
ca43d13bd6 Merge branch 'inject-properties-module-eager-singletons' into 'master'
Inject properties module's eager singletons

See merge request !568
2017-07-25 12:55:59 +00:00
akwizgran
5b71004179 Inject properties module's eager singletons. 2017-07-25 13:49:15 +01:00
akwizgran
63befccdbf Bumped expiry time and version number for beta release. 2017-07-21 11:52:09 +01:00
akwizgran
4ecf7c02d0 Merge branch '979-duplicate-blog-session' into 'master'
Fix Blog Sharing Sessions

Closes #979

See merge request !566
2017-07-21 10:27:21 +00:00
Torsten Grote
f25badc18c Move responsibility for pre-sharing blogs to sharing manager
to have all the code related to that in one place,
so it is easier to maintain and to spot bugs.

This also checks that only blogs without an existing sharing session
are shared and initialized again.
It extends an existing test to catch the missing check.

This removes some debugging information from the previous commit
to not leak private information via the sharing sessions.

Fixes #979
2017-07-17 14:07:47 -03:00
akwizgran
6e931e9ba5 Bump version number and expiry date for beta release. 2017-07-17 10:32:46 +01:00
akwizgran
7e749124bf Merge branch '617-protocol-versioning' into 'master'
Protocol versioning for BTP

See merge request !557
2017-07-17 09:24:35 +00:00
akwizgran
5822eb7808 Remove 'this'. 2017-07-17 10:16:50 +01:00
Torsten Grote
7a7e086541 Merge branch '982-name-not-found-exception' into 'master'
Use fully-qualified class names in manifest

Closes #982

See merge request !565
2017-07-14 12:19:50 +00:00
akwizgran
abab3167c2 Use fully-qualified class names in manifest.
This prevents a crash on Android 4 when the package name in build.gradle differs from the name in the manifest.
2017-07-14 12:01:55 +01:00
Torsten Grote
8d08570568 Merge branch '977-crash-when-opening-rss-blog' into 'master'
Create correct shareable for RSS blogs

Closes #977

See merge request !564
2017-07-07 17:42:54 +00:00
akwizgran
2007078f13 Added test for sharing an RSS blog. 2017-07-07 18:02:15 +01:00
akwizgran
dfb71a7978 Merge branch '942-block-blog-notifications' into 'master'
Block blog notifications when viewing combined feed

See merge request !563
2017-07-07 16:32:07 +00:00
akwizgran
480b0e3a03 Create correct shareable for RSS blogs.
Also removed "personal blog" wording that doesn't apply to RSS blogs.
2017-07-07 17:30:44 +01:00
akwizgran
8f8751f4ac Block blog notifications when viewing combined feed. 2017-07-07 15:34:00 +01:00
akwizgran
de2ea112ee Merge branch '933-beta-warning' into 'master'
Show Beta Expiry Warning

Closes #933

See merge request !559
2017-07-07 12:19:04 +00:00
Torsten Grote
6f99a53fd9 Show beta expiry warning in main activity 2017-07-07 09:12:07 -03:00
akwizgran
a8a9b9032d Merge branch 'warn_on_extra_translation' into 'master'
Show a warning instead of throwing an error for unused translations

See merge request !561
2017-07-07 12:01:23 +00:00
goapunk
6b15fb89de Show a warning instead of throwing an error for unused translations
Signed-off-by: goapunk <noobie@goapunks.net>
2017-07-07 12:19:44 +02:00
Torsten Grote
a711d6b8a1 Merge branch '106-fix-package-name' into 'master'
Set applicationId to match app_package

See merge request !560
2017-07-05 13:46:30 +00:00
akwizgran
5678f8aaa4 Update Robolectric tests so they can find resources. 2017-07-05 14:40:03 +01:00
akwizgran
2fe37f6c26 Set applicationId to match app_package. 2017-07-05 14:17:35 +01:00
Torsten Grote
a879747968 Translation Update 2017-07-05 09:50:14 -03:00
akwizgran
95e8fd7ee0 Merge branch 'notify_more_agressively' into 'master'
Notify more aggressively

See merge request !555
2017-07-05 11:36:37 +00:00
goapunk
4416aaaa4c Notify more aggressively
* Add setting to show notification on the lockscreen
* Don't block notifications in the contact-/-group/-forum/-bloglist

Signed-off-by: goapunk <noobie@goapunks.net>
2017-07-05 13:05:21 +02:00
Torsten Grote
500d5f0efe Merge branch '822-group-creation-workflow' into 'master'
Revisit private group creation workflow

Closes #822

See merge request !553
2017-07-04 17:19:11 +00:00
akwizgran
fc8978fd90 Create forum when button is clicked. 2017-07-04 16:17:59 +01:00
akwizgran
73df126bd4 Create and share private groups separately, as with forums. 2017-07-04 16:17:58 +01:00
akwizgran
9146488c7d Use same layout and behaviour for creating groups and forums. 2017-07-04 16:17:58 +01:00
akwizgran
613a7fe376 Merge branch '962-removing-contacts' into 'master'
Add test where two contacts remove each other

Closes #962

See merge request !558
2017-07-04 15:14:27 +00:00
akwizgran
ecb62f00d4 Code cleanup. 2017-07-04 16:09:32 +01:00
akwizgran
c4540a03cd Protocol versioning for BTP. 2017-07-04 16:09:32 +01:00
akwizgran
3e31da99b5 Merge branch '106-beta-namespace' into 'master'
Change app name and package name for beta release

Closes #106

See merge request !545
2017-07-04 14:40:47 +00:00
Torsten Grote
098c1d0b1e Add test where two contacts remove each other 2017-07-04 11:30:17 -03:00
akwizgran
178e908c86 Use a different package name and app name for beta builds. 2017-07-03 18:20:05 +01:00
akwizgran
ecf7cf14ae Merge branch '957-rss-url-case-sensitivity' into 'master'
Handle RSS URLs case-insensitively

Closes #957

See merge request !544
2017-07-03 16:22:52 +00:00
akwizgran
09e2a15a73 Merge branch '951-remove-visibility-indicators' into 'master'
Remove visibility indicators from private group join messages

Closes #951

See merge request !546
2017-07-03 16:22:31 +00:00
akwizgran
ab387860a6 Removed visibility indicators from private group member list. 2017-07-03 16:37:29 +01:00
akwizgran
f63fc94f2b Removed visibility indicators from private group join messages. 2017-07-03 16:37:29 +01:00
Michael Rogers
41e5928cca Validate and normalise RSS URLs. 2017-07-03 16:36:50 +01:00
Torsten Grote
8303175494 Merge branch 'use-f-droid-base-docker-image' into 'master'
Use F-Droid's base Docker image

See merge request !556
2017-07-03 15:35:28 +00:00
akwizgran
151eb6935b Use F-Droid's base Docker image. 2017-07-03 16:17:03 +01:00
akwizgran
6a419c0c7b Merge branch '968-downgrade-h2' into 'master'
Downgrade H2 to 1.4.192

Closes #968

See merge request !550
2017-07-03 11:51:35 +00:00
akwizgran
1795b32121 Downgrade H2 to 1.4.192. 2017-07-03 12:36:45 +01:00
akwizgran
01971768ce Merge branch '970_settings_use_summaries' into 'master'
Split notification settings into title and summary.

Closes #970

See merge request !554
2017-07-03 11:19:15 +00:00
akwizgran
ef7483ab01 Merge branch '787-tapping-blog-author-opens-same-blog' into 'master'
Don't reopen the same blog when the author is tapped

Closes #787

See merge request !551
2017-07-03 10:43:15 +00:00
akwizgran
527d11473d Merge branch '805-introduction-message-layout' into 'master'
Use smaller layout for introduction message screen

Closes #805

See merge request !552
2017-07-03 10:40:10 +00:00
akwizgran
775dadc9a0 Merge branch '904-notify-tor-controller' into 'master'
Notify Tor controller if Tor has crashed

Closes #904

See merge request !543
2017-07-03 09:29:04 +00:00
akwizgran
800b10a988 Merge branch '956-tap-protection-ux' into 'master'
Don't show tap protection dialog until it's needed

Closes #956

See merge request !548
2017-07-03 09:22:31 +00:00
akwizgran
c977bf047d Removed overrides of showScreenFilterWarning. 2017-07-03 10:08:36 +01:00
goapunk
660a25f21d Split notification settings into title and summary.
Signed-off-by: goapunk <noobie@goapunks.net>
2017-07-03 09:27:07 +02:00
Torsten Grote
e7fd6d23af Merge branch '962-check-blog-subscription-when-removing-contact' into 'master'
Check personal blog subscription when removing contact

See merge request !549
2017-06-30 20:57:52 +00:00
Torsten Grote
46982897f0 Merge branch '963-load-thread-list-messages-on-start' into 'master'
Load messages each time activity starts

See merge request !547
2017-06-30 20:53:13 +00:00
Torsten Grote
d24de68d64 Merge branch '574-upgrade-tor' into 'master'
Upgrade Tor to 0.2.9

Closes #574

See merge request !542
2017-06-30 20:36:22 +00:00
akwizgran
7514c46a3f Use smaller layout for introduction message screen. 2017-06-30 16:23:16 +01:00
akwizgran
6632c0f8e3 Don't reopen the same blog when the author is tapped. 2017-06-30 14:24:32 +01:00
akwizgran
79aafcda69 Fixed a test, added a regression test. 2017-06-30 12:58:44 +01:00
akwizgran
05af21e8dc Check personal blog subscription when removing contact. 2017-06-30 12:04:34 +01:00
akwizgran
0dc62cbbdc Fixed a test. 2017-06-30 10:01:35 +01:00
akwizgran
f3a084cfd2 Removed option to remember shown overlay apps. 2017-06-29 18:47:36 +01:00
akwizgran
8b32f82566 Don't show tap protection dialog until it's needed. 2017-06-29 18:18:39 +01:00
akwizgran
d598b6ed44 Load messages each time activity starts. 2017-06-26 16:21:25 +01:00
akwizgran
f5dc6f24b9 Bumped expiry date to 1 September 2017. 2017-06-26 14:50:58 +01:00
akwizgran
37454392da Update Tor binaries to 0.2.9.11. 2017-06-23 22:06:00 +01:00
akwizgran
de7f9111d3 Update Tor patch to 0.2.9.11. 2017-06-23 15:23:26 +01:00
akwizgran
96d2889a6c Notify Tor controller if Tor has crashed. 2017-06-23 14:57:00 +01:00
Torsten Grote
f6412d1e9a Merge branch 'spongy-castle-1-56' into 'master'
Upgrade Spongy Castle and some other dependencies

See merge request !541
2017-06-15 17:37:22 +00:00
akwizgran
b377cd6b1c Upgrade Spongy Castle and some other dependencies. 2017-06-12 17:44:08 +01:00
Ernir Erlingsson
f6cdbda5bb Merge branch 'master' of https://code.briarproject.org/akwizgran/briar 2017-05-29 11:06:16 +02:00
Ernir Erlingsson
855c600a3e hotfix list restore NPE for user testing 2017-05-29 11:05:46 +02:00
akwizgran
ea6e8303b0 Merge branch '954_dont_show_on_startup_failure' into 'master'
Don't show screenfilter warning in StartupFailureActivity

See merge request !539
2017-05-29 09:05:18 +00:00
goapunk
d4934040d9 Don't show screenfilter warning in StartupFailureActivity
Signed-off-by: goapunk <noobie@goapunks.net>
2017-05-22 12:40:33 +02:00
akwizgran
3449677b24 Bumped version number and expiry date. 2017-05-19 12:07:29 +01:00
akwizgran
1ad3a6646e Merge branch '941-store-correct-parent-id' into 'master'
Store correct original parent ID when rewrapping blog posts

See merge request !534
2017-05-12 09:53:27 +00:00
akwizgran
2d10f6b2bd Merge branch '884-emoji-text-view-layout-bug' into 'master'
Remove ellipsizing support from EmojiTextView

Closes #884

See merge request !533
2017-05-12 09:35:33 +00:00
akwizgran
5b05424d83 Merge branch 'master' into '941-store-correct-parent-id'
# Conflicts:
#   briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
2017-05-12 09:34:24 +00:00
akwizgran
0826022d82 Merge branch 'bring_annotations_in_line' into 'master'
Bring nullable annotation imports in line

See merge request !536
2017-05-12 09:33:00 +00:00
akwizgran
a901bfb9cb Merge branch '948-vector-crash' into 'master'
Remove scientific notation from vector drawables to prevent crashes

Closes #948

See merge request !537
2017-05-12 09:28:59 +00:00
akwizgran
03cdce122a Merge branch '947-bluetooth-address-crash' into 'master'
Don't crash on empty bluetooth addresses

See merge request !538
2017-05-12 09:26:57 +00:00
goapunk
f2e0e16969 Bring nullable annotation imports in line
Signed-off-by: goapunk <noobie@goapunks.net>
2017-05-12 10:06:56 +02:00
Torsten Grote
0c441e2ff3 Don't crash on empty bluetooth addresses 2017-05-10 15:06:09 -03:00
Torsten Grote
21302304a5 Remove scientific notation from vector drawables to prevent crashes
Details: http://stackoverflow.com/a/40829348
2017-05-10 14:56:59 -03:00
Torsten Grote
6839d8b844 Merge branch 'wifi-manager-memory-leak' into 'master'
Use application context to get WifiManager

See merge request !535
2017-05-10 17:01:52 +00:00
Torsten Grote
aee65a716c Merge branch '798-remove-contact-blogs' into 'master'
Allow to remove pre-shared blogs of our contacts

Closes #798

See merge request !529
2017-05-10 16:58:38 +00:00
Torsten Grote
6a07d8f2c9 Allow to remove pre-shared blogs of our contacts 2017-05-10 13:50:07 -03:00
Ernir Erlingsson
3c1ea81cd0 Merge branch '853-disabled-menu-items' into 'master'
Remove theme default color override

Closes #853

See merge request !527
2017-05-06 20:26:15 +00:00
Ernir Erlingsson
025f417bc7 Merge branch '894-list-position-restore' into 'master'
save and restore list position for threaded lists

Closes #894 and #946

See merge request !528
2017-05-06 19:37:02 +00:00
Ernir Erlingsson
c9dcd906c9 final pre-merge fixes 2017-05-06 21:36:25 +02:00
Ernir Erlingsson
7024e04d15 fixed final akwizgran comments 2017-05-06 21:31:53 +02:00
akwizgran
0b8ac947db Use application context to get WifiManager. 2017-05-05 15:43:27 +01:00
Ernir Erlingsson
948410a064 fixed unread buttons for threaded lists and akwizgran's comments 2017-05-05 14:49:53 +02:00
akwizgran
2841339cac Merge branch '468-ci' into 'master'
Set up basic CI

Closes #468

See merge request !530
2017-05-05 09:11:06 +00:00
Torsten Grote
e8e82bd805 Update Translations 2017-05-04 10:26:49 -03:00
Ernir Erlingsson
6876f40a0e Merge branch 'fix_groupname_validation' into 'master'
Fix groupname validation

See merge request !531
2017-05-04 07:26:55 +00:00
Ernir Erlingsson
5f4e1ecdfd improvements after code review #1
fix
2017-05-02 11:42:55 +02:00
Ernir Erlingsson
044719432a list position save and restore now implemented for threaded lists 2017-05-02 11:42:55 +02:00
Ernir Erlingsson
d1a929da85 bumped expire date 2017-05-02 11:42:15 +02:00
goapunk
2a8978a60d fix group name validation
Signed-off-by: goapunk <noobie@goapunks.net>
2017-04-29 16:49:37 +02:00
Torsten Grote
c0afad7a26 Set up basic CI 2017-04-28 13:24:41 -03:00
akwizgran
37281c6c23 Remove ellipsizing support from EmojiTextView.
This is a workaround for a layout bug.
2017-04-28 15:39:24 +01:00
akwizgran
76a5e25656 Added tests for wrapping and rewrapping blog posts. 2017-04-19 12:16:18 +01:00
akwizgran
3575b74837 Store correct original parent ID when rewrapping blog posts. 2017-04-19 12:15:34 +01:00
Torsten Grote
f1c7996960 Remove theme default color override 2017-04-18 09:14:00 -03:00
145 changed files with 3497 additions and 2092 deletions

20
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,20 @@
image: registry.gitlab.com/fdroid/ci-images-base:latest
cache:
paths:
- .gradle/wrapper
- .gradle/caches
before_script:
- export GRADLE_USER_HOME=$PWD/.gradle
# - export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
# - echo y | android --silent update sdk --no-ui --filter android-${ANDROID_COMPILE_SDK}
test:
script:
- ./gradlew test
after_script:
# this file changes every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/

View File

@@ -12,8 +12,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
versionCode 14
versionName "0.14"
consumerProguardFiles 'proguard-rules.txt'
}
@@ -25,38 +25,38 @@ android {
dependencies {
compile project(':bramble-core')
compile fileTree(dir: 'libs', include: ['*.jar'])
compile fileTree(dir: 'libs', include: '*.jar')
provided 'javax.annotation:jsr250-api:1.0'
}
def torBinaryDir = 'src/main/res/raw'
task downloadTorGeoIp(type: Download) {
src 'https://briarproject.org/build/geoip-2015-12-01.zip'
src 'https://briarproject.org/build/geoip-2017-05-02.zip'
dest "$torBinaryDir/geoip.zip"
onlyIfNewer true
}
task downloadTorBinaryArm(type: Download) {
src 'https://briarproject.org/build/tor-0.2.7.6-arm.zip'
src 'https://briarproject.org/build/tor-0.2.9.11-arm.zip'
dest "$torBinaryDir/tor_arm.zip"
onlyIfNewer true
}
task downloadTorBinaryArmPie(type: Download) {
src 'https://briarproject.org/build/tor-0.2.7.6-arm-pie.zip'
src 'https://briarproject.org/build/tor-0.2.9.11-arm-pie.zip'
dest "$torBinaryDir/tor_arm_pie.zip"
onlyIfNewer true
}
task downloadTorBinaryX86(type: Download) {
src 'https://briarproject.org/build/tor-0.2.7.6-x86.zip'
src 'https://briarproject.org/build/tor-0.2.9.11-x86.zip'
dest "$torBinaryDir/tor_x86.zip"
onlyIfNewer true
}
task downloadTorBinaryX86Pie(type: Download) {
src 'https://briarproject.org/build/tor-0.2.7.6-x86-pie.zip'
src 'https://briarproject.org/build/tor-0.2.9.11-x86-pie.zip'
dest "$torBinaryDir/tor_x86_pie.zip"
onlyIfNewer true
}
@@ -64,31 +64,31 @@ task downloadTorBinaryX86Pie(type: Download) {
task verifyTorGeoIp(type: Verify, dependsOn: 'downloadTorGeoIp') {
src "$torBinaryDir/geoip.zip"
algorithm 'SHA-256'
checksum '9bcdaf0a7ba0933735328d8ec466c25c25dbb459efc2bce9e55c774eabea5162'
checksum '51f4d1272fb867e1f3b36b67a584e2a33c40b40f62305457d799fd399cd77c9b'
}
task verifyTorBinaryArm(type: Verify, dependsOn: 'downloadTorBinaryArm') {
src "$torBinaryDir/tor_arm.zip"
algorithm 'SHA-256'
checksum '83272962eda701cd5d74d2418651c4ff0f0b1dff51f558a292d1a1c42bf12146'
checksum '1da6008663a8ad98b349e62acbbf42c379f65ec504fa467cb119c187cd5a4c6b'
}
task verifyTorBinaryArmPie(type: Verify, dependsOn: 'downloadTorBinaryArmPie') {
src "$torBinaryDir/tor_arm_pie.zip"
algorithm 'SHA-256'
checksum 'd0300d1e45de11ebb24ed62b9c492be9c2e88590b7822195ab38c7a76ffcf646'
checksum 'eb061f880829e05f104690ac744848133f2dacef04759d425a2cff0df32c271e'
}
task verifyTorBinaryX86(type: Verify, dependsOn: 'downloadTorBinaryX86') {
src "$torBinaryDir/tor_x86.zip"
algorithm 'SHA-256'
checksum 'b8813d97b01ee1b9c9a4233c1b9bbe9f9f6b494ae6f9cbd84de8a3911911615e'
checksum 'f5308aff8303daca082f82227d02b51ddedba4ab1d1420739ada0427ae5dbb41'
}
task verifyTorBinaryX86Pie(type: Verify, dependsOn: 'downloadTorBinaryX86Pie') {
src "$torBinaryDir/tor_x86_pie.zip"
algorithm 'SHA-256'
checksum '9c66e765aa196dc089951a1b2140cc8290305c2fcbf365121f99e01a233baf4e'
checksum '889a6c81ac73d05d35ed610ca5a913cee44d333e4ae1749c2a107f2f7dd8197b'
}
project.afterEvaluate {

View File

@@ -19,7 +19,8 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.AbstractBluetoothPlugin;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
@@ -74,8 +75,7 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class DroidtoothPlugin<C, S>
extends AbstractBluetoothPlugin<C, S>{
class DroidtoothPlugin implements DuplexPlugin {
private static final Logger LOG =
Logger.getLogger(DroidtoothPlugin.class.getName());
@@ -84,10 +84,16 @@ class DroidtoothPlugin<C, S>
private static final String DISCOVERY_FINISHED =
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
private final Executor ioExecutor;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile boolean wasEnabledByUs = false;
private volatile BluetoothStateReceiver receiver = null;
private volatile BluetoothServerSocket socket = null;
@@ -98,15 +104,29 @@ class DroidtoothPlugin<C, S>
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, maxLatency, callback);
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
}
@Override
protected void close(S ss) throws IOException {
((BluetoothServerSocket)ss).close();
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
@@ -174,14 +194,14 @@ class DroidtoothPlugin<C, S>
BluetoothServerSocket ss;
try {
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", UUID.fromString(getUuid()));
"RFCOMM", getUuid());
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
return;
}
if (!isRunning()) {
tryToClose((S)ss);
tryToClose(ss);
return;
}
LOG.info("Socket bound");
@@ -193,6 +213,29 @@ class DroidtoothPlugin<C, S>
});
}
private UUID getUuid() {
String uuid = callback.getLocalProperties().get(PROP_UUID);
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
}
return UUID.fromString(uuid);
}
private void tryToClose(@Nullable BluetoothServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
private void acceptContactConnections() {
while (isRunning()) {
BluetoothSocket s;
@@ -218,9 +261,9 @@ class DroidtoothPlugin<C, S>
@Override
public void stop() {
this.running = false;
running = false;
if (receiver != null) appContext.unregisterReceiver(receiver);
tryToClose((S)socket);
tryToClose(socket);
// Disable Bluetooth if we enabled it and it's still enabled
if (wasEnabledByUs && adapter.isEnabled()) {
if (adapter.disable()) LOG.info("Disabling Bluetooth");
@@ -233,20 +276,42 @@ class DroidtoothPlugin<C, S>
return running && adapter != null && adapter.isEnabled();
}
protected Runnable returnPollRunnable(final String address, final String uuid,
final ContactId c) {
return new Runnable() {
@Override
public void run() {
if (!running) return;
BluetoothSocket s = connect(address, uuid);
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
}
@Override
public boolean shouldPoll() {
return true;
}
};
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<ContactId> connected) {
if (!isRunning()) return;
backoff.increment();
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
final ContactId c = e.getKey();
if (connected.contains(c)) continue;
final String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
BluetoothSocket s = connect(address, uuid);
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
}
});
}
}
@Nullable
@@ -282,17 +347,37 @@ class DroidtoothPlugin<C, S>
LOG.info("Failed to connect to " + scrubMacAddress(address)
+ ": " + e);
}
tryToClose((S)s);
tryToClose(s);
return null;
}
}
public DuplexTransportConnection connectToAddress(String address, String uuid) {
BluetoothSocket s = connect(address, uuid);
return s == null ? null : wrapSocket(s);
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
BluetoothSocket s = connect(address, uuid);
if (s == null) return null;
return new DroidtoothTransportConnection(this, s);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
@@ -341,7 +426,7 @@ class DroidtoothPlugin<C, S>
return null;
} finally {
// Closing the socket will terminate the listener task
tryToClose((S)ss);
tryToClose(ss);
closeSockets(futures, chosen);
}
}
@@ -373,6 +458,11 @@ class DroidtoothPlugin<C, S>
});
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) return null;
@@ -397,6 +487,31 @@ class DroidtoothPlugin<C, S>
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null;
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
// No truncation necessary because COMMIT_LENGTH = 16
UUID uuid = UUID.nameUUIDFromBytes(commitment);
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
BluetoothSocket s = connect(address, uuid.toString());
if (s == null) return null;
return new DroidtoothTransportConnection(this, s);
}
private String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return StringUtils.macToString(mac);
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@@ -408,7 +523,7 @@ class DroidtoothPlugin<C, S>
bind();
} else if (state == STATE_OFF) {
LOG.info("Bluetooth disabled");
tryToClose((S)socket);
tryToClose(socket);
}
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
if (scanMode == SCAN_MODE_NONE) {

View File

@@ -7,12 +7,12 @@ apply plugin: 'witness'
dependencies {
compile "com.google.dagger:dagger:2.0.2"
compile 'com.google.dagger:dagger-compiler:2.0.2'
compile 'com.google.code.findbugs:jsr305:3.0.1'
compile 'com.google.code.findbugs:jsr305:3.0.2'
testCompile 'junit:junit:4.12'
testCompile "org.jmock:jmock:2.8.1"
testCompile "org.jmock:jmock-junit4:2.8.1"
testCompile "org.jmock:jmock-legacy:2.8.1"
testCompile "org.jmock:jmock:2.8.2"
testCompile "org.jmock:jmock-junit4:2.8.2"
testCompile "org.jmock:jmock-legacy:2.8.2"
testCompile "org.hamcrest:hamcrest-library:1.3"
testCompile "org.hamcrest:hamcrest-core:1.3"
}
@@ -21,7 +21,7 @@ dependencyVerification {
verify = [
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.code.findbugs:jsr305:c885ce34249682bc0236b4a7d56efcc12048e6135a5baf7a9cde8ad8cda13fcd',
'com.google.code.findbugs:jsr305:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import java.util.Arrays;
import java.util.Comparator;
@@ -53,6 +54,12 @@ public class Bytes implements Comparable<Bytes> {
return aBytes.length - bBytes.length;
}
@Override
public String toString() {
return getClass().getSimpleName() +
"(" + StringUtils.toHexString(getBytes()) + ")";
}
public static class BytesComparator implements Comparator<Bytes> {
@Override

View File

@@ -137,7 +137,8 @@ public interface CryptoComponent {
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
/** Encodes the pseudo-random tag that is used to recognise a stream. */
void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber);
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber);
/**
* Signs the given byte[] with the given PrivateKey.

View File

@@ -1,190 +0,0 @@
package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.StringUtils;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* Created by Santiago Torres-Arias on 1/10/17.
*/
public abstract class AbstractBluetoothPlugin<C, S> implements DuplexPlugin {
private static final Logger LOG =
Logger.getLogger("Halp");
protected final Executor ioExecutor;
protected final SecureRandom secureRandom;
protected final Backoff backoff;
protected final int maxLatency;
protected final DuplexPluginCallback callback;
protected final S ss = null;
protected volatile boolean running = false;
protected Runnable pollRunnable = null;
public AbstractBluetoothPlugin(Executor ioExecutor,
SecureRandom secureRandom,
Backoff backoff, int maxLatency,
DuplexPluginCallback callback) {
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.maxLatency = maxLatency;
this.callback = callback;
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
protected String getUuid() {
String uuid = callback.getLocalProperties().get(PROP_UUID);
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
}
return uuid;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
protected String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return StringUtils.macToString(mac);
}
protected void tryToClose(@Nullable S ss) {
try {
if (ss != null) close(ss);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
protected abstract void close(S ss) throws IOException;
public void stop() {
running = false;
tryToClose(ss);
}
@Override
public void poll(final Collection<ContactId> connected) {
if (!running) return;
backoff.increment();
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for (Map.Entry<ContactId, TransportProperties> e : remote.entrySet()) {
final ContactId c = e.getKey();
if (connected.contains(c)) continue;
final String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(returnPollRunnable(address,uuid, c));
}
}
protected abstract Runnable returnPollRunnable(String address, String uuid,
ContactId c);
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
return connectToAddress(address, uuid);
}
protected abstract DuplexTransportConnection connectToAddress(String address, String uuid);
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null;
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
return connectToAddress(address, uuid);
}
}

View File

@@ -4,6 +4,11 @@ import org.briarproject.bramble.api.crypto.SecretKey;
public interface TransportConstants {
/**
* The current version of the transport protocol.
*/
int PROTOCOL_VERSION = 3;
/**
* The length of the pseudo-random tag in bytes.
*/
@@ -14,21 +19,22 @@ public interface TransportConstants {
*/
int STREAM_HEADER_NONCE_LENGTH = 24;
/**
* The length of the stream header initialisation vector (IV) in bytes.
*/
int STREAM_HEADER_IV_LENGTH = STREAM_HEADER_NONCE_LENGTH - 8;
/**
* The length of the message authentication code (MAC) in bytes.
*/
int MAC_LENGTH = 16;
/**
* The length of the stream header plaintext in bytes. The stream header
* contains the protocol version, stream number and frame key.
*/
int STREAM_HEADER_PLAINTEXT_LENGTH = 2 + 8 + SecretKey.LENGTH;
/**
* The length of the stream header in bytes.
*/
int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH
+ MAC_LENGTH;
int STREAM_HEADER_LENGTH = STREAM_HEADER_NONCE_LENGTH
+ STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH;
/**
* The length of the frame nonce in bytes.

View File

@@ -19,7 +19,7 @@ public class PrivacyUtils {
@Nullable
public static String scrubMacAddress(@Nullable String address) {
if (address == null) return null;
if (address == null || address.length() == 0) return null;
// this is a fake address we need to know about
if (address.equals("02:00:00:00:00:00")) return address;
// keep first and last octet of MAC address

View File

@@ -1,8 +1,9 @@
plugins {
id "java"
id "net.ltgt.apt" version "0.9"
id "idea"
id 'java'
id 'net.ltgt.apt' version '0.9'
id 'idea'
}
sourceCompatibility = 1.6
targetCompatibility = 1.6
@@ -10,17 +11,18 @@ apply plugin: 'witness'
dependencies {
compile project(':bramble-api')
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.madgag.spongycastle:core:1.54.0.0'
compile 'com.h2database:h2:1.4.190'
compile 'com.madgag.spongycastle:core:1.56.0.0'
compile 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
compile 'org.bitlet:weupnp:0.1.4'
testCompile project(path: ':bramble-api', configuration: 'testOutput')
}
dependencyVerification {
verify = [
'com.madgag.spongycastle:core:1e7fa4b19ccccd1011364ab838d0b4702470c178bbbdd94c5c90b2d4d749ea1e',
'com.h2database:h2:23ba495a07bbbb3bd6c3084d10a96dad7a23741b8b6d64b213459a784195a98c'
'com.madgag.spongycastle:core:5e791b0eaa9e0c4594231b44f616a52adddb7dccedeb0ad9ad74887e19499a23',
'com.h2database:h2:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
'org.bitlet:weupnp:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
]
}

View File

@@ -54,6 +54,7 @@ public class BrambleCoreModule {
c.inject(new IdentityModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons());
c.inject(new SyncModule.EagerSingletons());
c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons());

View File

@@ -45,8 +45,10 @@ import static org.briarproject.bramble.api.invitation.InvitationConstants.CODE_B
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
class CryptoComponentImpl implements CryptoComponent {
@@ -412,8 +414,11 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber) {
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber) {
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
throw new IllegalArgumentException();
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
// Initialise the PRF
@@ -421,10 +426,14 @@ class CryptoComponentImpl implements CryptoComponent {
// The output of the PRF must be long enough to use as a tag
int macLength = prf.getDigestSize();
if (macLength < TAG_LENGTH) throw new IllegalStateException();
// The input is the stream number as a 64-bit integer
byte[] input = new byte[INT_64_BYTES];
ByteUtils.writeUint64(streamNumber, input, 0);
prf.update(input, 0, input.length);
// The input is the protocol version as a 16-bit integer, followed by
// the stream number as a 64-bit integer
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
byte[] streamNumberBytes = new byte[INT_64_BYTES];
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
byte[] mac = new byte[macLength];
prf.doFinal(mac, 0);
// The output is the first TAG_LENGTH bytes of the MAC

View File

@@ -20,9 +20,11 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_NO
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
@NotThreadSafe
@@ -117,7 +119,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
private void readStreamHeader() throws IOException {
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
byte[] streamHeaderPlaintext = new byte[SecretKey.LENGTH];
byte[] streamHeaderPlaintext = new byte[STREAM_HEADER_PLAINTEXT_LENGTH];
// Read the stream header
int offset = 0;
while (offset < STREAM_HEADER_LENGTH) {
@@ -126,21 +128,35 @@ class StreamDecrypterImpl implements StreamDecrypter {
if (read == -1) throw new EOFException();
offset += read;
}
// The nonce consists of the stream number followed by the IV
// Extract the nonce
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
ByteUtils.writeUint64(streamNumber, streamHeaderNonce, 0);
System.arraycopy(streamHeaderCiphertext, 0, streamHeaderNonce,
INT_64_BYTES, STREAM_HEADER_IV_LENGTH);
System.arraycopy(streamHeaderCiphertext, 0, streamHeaderNonce, 0,
STREAM_HEADER_NONCE_LENGTH);
// Decrypt and authenticate the stream header
try {
cipher.init(false, streamHeaderKey, streamHeaderNonce);
int decrypted = cipher.process(streamHeaderCiphertext,
STREAM_HEADER_IV_LENGTH, SecretKey.LENGTH + MAC_LENGTH,
STREAM_HEADER_NONCE_LENGTH,
STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH,
streamHeaderPlaintext, 0);
if (decrypted != SecretKey.LENGTH) throw new RuntimeException();
if (decrypted != STREAM_HEADER_PLAINTEXT_LENGTH)
throw new RuntimeException();
} catch (GeneralSecurityException e) {
throw new FormatException();
}
frameKey = new SecretKey(streamHeaderPlaintext);
// Check the protocol version
int receivedProtocolVersion =
ByteUtils.readUint16(streamHeaderPlaintext, 0);
if (receivedProtocolVersion != PROTOCOL_VERSION)
throw new FormatException();
// Check the stream number
long receivedStreamNumber = ByteUtils.readUint64(streamHeaderPlaintext,
INT_16_BYTES);
if (receivedStreamNumber != streamNumber) throw new FormatException();
// Extract the frame key
byte[] frameKeyBytes = new byte[SecretKey.LENGTH];
System.arraycopy(streamHeaderPlaintext, INT_16_BYTES + INT_64_BYTES,
frameKeyBytes, 0, SecretKey.LENGTH);
frameKey = new SecretKey(frameKeyBytes);
}
}

View File

@@ -13,7 +13,8 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.inject.Provider;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
@Immutable
@@ -36,22 +37,22 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
AuthenticatedCipher cipher = cipherProvider.get();
long streamNumber = ctx.getStreamNumber();
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, ctx.getTagKey(), streamNumber);
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
crypto.getSecureRandom().nextBytes(streamHeaderIv);
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
SecretKey frameKey = crypto.generateSecretKey();
return new StreamEncrypterImpl(out, cipher, streamNumber, tag,
streamHeaderIv, ctx.getHeaderKey(), frameKey);
streamHeaderNonce, ctx.getHeaderKey(), frameKey);
}
@Override
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
SecretKey headerKey) {
AuthenticatedCipher cipher = cipherProvider.get();
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
crypto.getSecureRandom().nextBytes(streamHeaderIv);
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
SecretKey frameKey = crypto.generateSecretKey();
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderIv,
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
headerKey, frameKey);
}
}

View File

@@ -18,9 +18,11 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_NO
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
@NotThreadSafe
@@ -33,7 +35,7 @@ class StreamEncrypterImpl implements StreamEncrypter {
private final long streamNumber;
@Nullable
private final byte[] tag;
private final byte[] streamHeaderIv;
private final byte[] streamHeaderNonce;
private final byte[] frameNonce, frameHeader;
private final byte[] framePlaintext, frameCiphertext;
@@ -41,13 +43,13 @@ class StreamEncrypterImpl implements StreamEncrypter {
private boolean writeTag, writeStreamHeader;
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher cipher,
long streamNumber, @Nullable byte[] tag, byte[] streamHeaderIv,
long streamNumber, @Nullable byte[] tag, byte[] streamHeaderNonce,
SecretKey streamHeaderKey, SecretKey frameKey) {
this.out = out;
this.cipher = cipher;
this.streamNumber = streamNumber;
this.tag = tag;
this.streamHeaderIv = streamHeaderIv;
this.streamHeaderNonce = streamHeaderNonce;
this.streamHeaderKey = streamHeaderKey;
this.frameKey = frameKey;
frameNonce = new byte[FRAME_NONCE_LENGTH];
@@ -114,22 +116,23 @@ class StreamEncrypterImpl implements StreamEncrypter {
}
private void writeStreamHeader() throws IOException {
// The nonce consists of the stream number followed by the IV
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
ByteUtils.writeUint64(streamNumber, streamHeaderNonce, 0);
System.arraycopy(streamHeaderIv, 0, streamHeaderNonce, INT_64_BYTES,
STREAM_HEADER_IV_LENGTH);
byte[] streamHeaderPlaintext = frameKey.getBytes();
// The header contains the protocol version, stream number and frame key
byte[] streamHeaderPlaintext = new byte[STREAM_HEADER_PLAINTEXT_LENGTH];
ByteUtils.writeUint16(PROTOCOL_VERSION, streamHeaderPlaintext, 0);
ByteUtils.writeUint64(streamNumber, streamHeaderPlaintext,
INT_16_BYTES);
System.arraycopy(frameKey.getBytes(), 0, streamHeaderPlaintext,
INT_16_BYTES + INT_64_BYTES, SecretKey.LENGTH);
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
System.arraycopy(streamHeaderIv, 0, streamHeaderCiphertext, 0,
STREAM_HEADER_IV_LENGTH);
// Encrypt and authenticate the frame key
System.arraycopy(streamHeaderNonce, 0, streamHeaderCiphertext, 0,
STREAM_HEADER_NONCE_LENGTH);
// Encrypt and authenticate the stream header key
try {
cipher.init(true, streamHeaderKey, streamHeaderNonce);
int encrypted = cipher.process(streamHeaderPlaintext, 0,
SecretKey.LENGTH, streamHeaderCiphertext,
STREAM_HEADER_IV_LENGTH);
if (encrypted != SecretKey.LENGTH + MAC_LENGTH)
STREAM_HEADER_PLAINTEXT_LENGTH, streamHeaderCiphertext,
STREAM_HEADER_NONCE_LENGTH);
if (encrypted != STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH)
throw new RuntimeException();
} catch (GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher);

View File

@@ -70,25 +70,7 @@ class XSalsa20Poly1305AuthenticatedCipher implements AuthenticatedCipher {
byte[] subKey = new byte[SUBKEY_LENGTH];
xSalsa20Engine.processBytes(zero, 0, SUBKEY_LENGTH, subKey, 0);
// Reverse the order of the Poly130 subkey
//
// NaCl and libsodium use the first 32 bytes of XSalsa20 as the
// subkey for crypto_onetimeauth_poly1305, which interprets it
// as r[0] ... r[15], k[0] ... k[15]. See section 9 of the NaCl
// paper (http://cr.yp.to/highspeed/naclcrypto-20090310.pdf),
// where the XSalsa20 output is defined as (r, s, t, ...).
//
// BC's Poly1305 implementation interprets the subkey as
// k[0] ... k[15], r[0] ... r[15] (per poly1305_aes_clamp in
// the reference implementation).
//
// To be NaCl-compatible, we reverse the subkey.
System.arraycopy(subKey, 0, zero, 0, SUBKEY_LENGTH / 2);
System.arraycopy(subKey, SUBKEY_LENGTH / 2, subKey, 0,
SUBKEY_LENGTH / 2);
System.arraycopy(zero, 0, subKey, SUBKEY_LENGTH / 2,
SUBKEY_LENGTH / 2);
// Now we can clamp the correct part of the subkey
// Clamp the subkey
Poly1305KeyGenerator.clamp(subKey);
// Initialize Poly1305 with the subkey

View File

@@ -29,6 +29,7 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
@@ -126,7 +127,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
for (long streamNumber : inKeys.getWindow().getUnseen()) {
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, inKeys.getTagKey(), streamNumber);
crypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
streamNumber);
inContexts.put(new Bytes(tag), tagCtx);
}
}
@@ -242,7 +244,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
// Add tags for any stream numbers added to the window
for (long streamNumber : change.getAdded()) {
byte[] addTag = new byte[TAG_LENGTH];
crypto.encodeTag(addTag, inKeys.getTagKey(), streamNumber);
crypto.encodeTag(addTag, inKeys.getTagKey(), PROTOCOL_VERSION,
streamNumber);
inContexts.put(new Bytes(addTag), new TagContext(
tagCtx.contactId, inKeys, streamNumber));
}
@@ -250,7 +253,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
for (long streamNumber : change.getRemoved()) {
if (streamNumber == tagCtx.streamNumber) continue;
byte[] removeTag = new byte[TAG_LENGTH];
crypto.encodeTag(removeTag, inKeys.getTagKey(), streamNumber);
crypto.encodeTag(removeTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
inContexts.remove(new Bytes(removeTag));
}
// Write the window back to the DB

View File

@@ -14,7 +14,8 @@ import static junit.framework.Assert.assertEquals;
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
import static org.junit.Assert.assertArrayEquals;
@@ -22,7 +23,8 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
private final AuthenticatedCipher cipher;
private final SecretKey streamHeaderKey, frameKey;
private final byte[] streamHeaderIv, payload;
private final byte[] streamHeaderNonce, protocolVersionBytes;
private final byte[] streamNumberBytes, payload;
private final int payloadLength = 123, paddingLength = 234;
private final long streamNumber = 1234;
@@ -30,7 +32,12 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
cipher = new TestAuthenticatedCipher(); // Null cipher
streamHeaderKey = TestUtils.getSecretKey();
frameKey = TestUtils.getSecretKey();
streamHeaderIv = TestUtils.getRandomBytes(STREAM_HEADER_IV_LENGTH);
streamHeaderNonce =
TestUtils.getRandomBytes(STREAM_HEADER_NONCE_LENGTH);
protocolVersionBytes = new byte[2];
ByteUtils.writeUint16(PROTOCOL_VERSION, protocolVersionBytes, 0);
streamNumberBytes = new byte[8];
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
payload = TestUtils.getRandomBytes(payloadLength);
}
@@ -47,7 +54,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(streamHeaderNonce);
out.write(protocolVersionBytes);
out.write(streamNumberBytes);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
@@ -76,6 +85,85 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
assertEquals(-1, s.readFrame(buffer));
}
@Test(expected = IOException.class)
public void testWrongProtocolVersionThrowsException() throws Exception {
byte[] wrongProtocolVersionBytes = new byte[2];
ByteUtils.writeUint16(PROTOCOL_VERSION + 1, wrongProtocolVersionBytes,
0);
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
paddingLength);
byte[] frameHeader1 = new byte[FRAME_HEADER_LENGTH];
int payloadLength1 = 345, paddingLength1 = 456;
FrameEncoder.encodeHeader(frameHeader1, true, payloadLength1,
paddingLength1);
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderNonce);
out.write(wrongProtocolVersionBytes);
out.write(streamNumberBytes);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader1);
out.write(payload1);
out.write(new byte[paddingLength1]);
out.write(new byte[MAC_LENGTH]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamNumber, streamHeaderKey);
// Try to read the first frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
s.readFrame(buffer);
}
@Test(expected = IOException.class)
public void testWrongStreamNumberThrowsException() throws Exception {
byte[] wrongStreamNumberBytes = new byte[8];
ByteUtils.writeUint64(streamNumber + 1, wrongStreamNumberBytes, 0);
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
paddingLength);
byte[] frameHeader1 = new byte[FRAME_HEADER_LENGTH];
int payloadLength1 = 345, paddingLength1 = 456;
FrameEncoder.encodeHeader(frameHeader1, true, payloadLength1,
paddingLength1);
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderNonce);
out.write(protocolVersionBytes);
out.write(wrongStreamNumberBytes);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader1);
out.write(payload1);
out.write(new byte[paddingLength1]);
out.write(new byte[MAC_LENGTH]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamNumber, streamHeaderKey);
// Try to read the first frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
s.readFrame(buffer);
}
@Test(expected = IOException.class)
public void testTruncatedFrameThrowsException() throws Exception {
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -83,7 +171,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
paddingLength);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(streamHeaderNonce);
out.write(protocolVersionBytes);
out.write(streamNumberBytes);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
@@ -111,7 +201,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
byte[] payload = TestUtils.getRandomBytes(payloadLength);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(streamHeaderNonce);
out.write(protocolVersionBytes);
out.write(streamNumberBytes);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
@@ -138,7 +230,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
padding[paddingLength - 1] = 1;
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(streamHeaderNonce);
out.write(protocolVersionBytes);
out.write(streamNumberBytes);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
@@ -162,7 +256,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
paddingLength);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(streamHeaderNonce);
out.write(protocolVersionBytes);
out.write(streamNumberBytes);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -11,8 +12,9 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HE
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -21,7 +23,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
private final AuthenticatedCipher cipher;
private final SecretKey streamHeaderKey, frameKey;
private final byte[] tag, streamHeaderIv, payload;
private final byte[] tag, streamHeaderNonce, protocolVersionBytes;
private final byte[] streamNumberBytes, payload;
private final long streamNumber = 1234;
private final int payloadLength = 123, paddingLength = 234;
@@ -30,7 +33,12 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
streamHeaderKey = TestUtils.getSecretKey();
frameKey = TestUtils.getSecretKey();
tag = TestUtils.getRandomBytes(TAG_LENGTH);
streamHeaderIv = TestUtils.getRandomBytes(STREAM_HEADER_IV_LENGTH);
streamHeaderNonce =
TestUtils.getRandomBytes(STREAM_HEADER_NONCE_LENGTH);
protocolVersionBytes = new byte[2];
ByteUtils.writeUint16(PROTOCOL_VERSION, protocolVersionBytes, 0);
streamNumberBytes = new byte[8];
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
payload = TestUtils.getRandomBytes(payloadLength);
}
@@ -38,7 +46,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testRejectsNegativePayloadLength() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, -1, 0, false);
}
@@ -47,7 +56,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testRejectsNegativePaddingLength() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, 0, -1, false);
}
@@ -56,7 +66,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testRejectsMaxPayloadPlusPadding() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH + 1];
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 1, false);
@@ -66,7 +77,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testAcceptsMaxPayloadIncludingPadding() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH - 1, 1, false);
@@ -78,7 +90,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testAcceptsMaxPayloadWithoutPadding() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 0, false);
@@ -90,14 +103,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, payloadLength, 0, false);
// Expect the tag, stream header, frame header, payload and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -113,14 +129,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWriteUnpaddedFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, payloadLength, 0, true);
// Expect the tag, stream header, frame header, payload and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -136,13 +155,16 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWriteUnpaddedNonFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, null, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, payloadLength, 0, false);
// Expect the stream header, frame header, payload and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -158,13 +180,16 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWriteUnpaddedFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, null, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, payloadLength, 0, true);
// Expect the stream header, frame header, payload and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -180,14 +205,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWritePaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, payloadLength, paddingLength, false);
// Expect the tag, stream header, frame header, payload, padding and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -205,14 +233,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWritePaddedFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, payloadLength, paddingLength, true);
// Expect the tag, stream header, frame header, payload, padding and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -230,13 +261,16 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWritePaddedNonFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, null, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, payloadLength, paddingLength, false);
// Expect the stream header, frame header, payload, padding and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -254,13 +288,16 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWritePaddedFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, null, streamHeaderNonce, streamHeaderKey,
frameKey);
s.writeFrame(payload, payloadLength, paddingLength, true);
// Expect the stream header, frame header, payload, padding and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -278,7 +315,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testWriteTwoFramesWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
int payloadLength1 = 345, paddingLength1 = 456;
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
@@ -289,7 +327,9 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
// MAC, second frame header, payload, padding, MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -315,7 +355,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
// Flush the stream once
s.flush();
@@ -323,7 +364,9 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
// Expect the tag and stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
@@ -335,7 +378,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, tag, streamHeaderNonce, streamHeaderKey,
frameKey);
// Flush the stream twice
s.flush();
@@ -344,7 +388,9 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
// Expect the tag and stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
@@ -355,14 +401,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
public void testFlushDoesNotWriteTagIfNull() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
streamNumber, null, streamHeaderNonce, streamHeaderKey,
frameKey);
// Flush the stream once
s.flush();
// Expect the stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(streamHeaderNonce);
expected.write(protocolVersionBytes);
expected.write(streamNumberBytes);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);

View File

@@ -0,0 +1,59 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
public class TagEncodingTest extends BrambleTestCase {
private final CryptoComponent crypto;
private final SecretKey tagKey;
private final long streamNumber = 1234567890;
public TagEncodingTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
tagKey = TestUtils.getSecretKey();
}
@Test
public void testKeyAffectsTag() throws Exception {
Set<Bytes> set = new HashSet<Bytes>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
SecretKey tagKey = TestUtils.getSecretKey();
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber);
assertTrue(set.add(new Bytes(tag)));
}
}
@Test
public void testProtocolVersionAffectsTag() throws Exception {
Set<Bytes> set = new HashSet<Bytes>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
assertTrue(set.add(new Bytes(tag)));
}
}
@Test
public void testStreamNumberAffectsTag() throws Exception {
Set<Bytes> set = new HashSet<Bytes>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);
assertTrue(set.add(new Bytes(tag)));
}
}
}

View File

@@ -34,6 +34,7 @@ import java.util.Collection;
import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -115,7 +116,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
private void read(byte[] connectionData) throws Exception {
// Calculate the expected tag
byte[] expectedTag = new byte[TAG_LENGTH];
crypto.encodeTag(expectedTag, tagKey, streamNumber);
crypto.encodeTag(expectedTag, tagKey, PROTOCOL_VERSION, streamNumber);
// Read the tag
InputStream in = new ByteArrayInputStream(connectionData);

View File

@@ -33,6 +33,7 @@ import java.util.concurrent.ScheduledExecutorService;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
@@ -86,7 +87,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
// Encode the tags (3 sets per contact)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(6).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
with(tagKey), with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys that were rotated
@@ -133,7 +134,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
with(tagKey), with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys
@@ -199,7 +200,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
with(tagKey), with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
@@ -247,7 +248,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
with(tagKey), with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
@@ -306,7 +307,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
with(tagKey), with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
@@ -355,7 +356,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
with(tagKey), with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction(tags));
}
// Rotate the transport keys (the keys are unaffected)
@@ -365,7 +366,8 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
// Encode a new tag after sliding the window
oneOf(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with((long) REORDERING_WINDOW_SIZE));
with(tagKey), with(PROTOCOL_VERSION),
with((long) REORDERING_WINDOW_SIZE));
will(new EncodeTagAction(tags));
// Save the reordering window (previous rotation period, base 1)
oneOf(db).setReorderingWindow(txn, contactId, transportId, 999,
@@ -428,7 +430,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
with(tagKey), with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Schedule key rotation at the start of the next rotation period
@@ -450,7 +452,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
with(tagKey), with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys that were rotated

View File

@@ -7,10 +7,19 @@ apply plugin: 'witness'
dependencies {
compile project(':bramble-core')
compile fileTree(dir: 'libs', include: '*.jar')
compile 'net.java.dev.jna:jna:4.4.0'
compile 'net.java.dev.jna:jna-platform:4.4.0'
testCompile project(path: ':bramble-core', configuration: 'testOutput')
}
dependencyVerification {
verify = [
'net.java.dev.jna:jna:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1',
'net.java.dev.jna:jna-platform:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499',
]
}
tasks.withType(Test) {
systemProperty 'java.library.path', 'libs'
}

Binary file not shown.

View File

@@ -10,14 +10,14 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.AbstractBluetoothPlugin;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.OsUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
@@ -57,22 +57,46 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
class BluetoothPlugin implements DuplexPlugin {
private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName());
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final Semaphore discoverySemaphore = new Semaphore(1);
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile StreamConnectionNotifier socket = null;
private volatile LocalDevice localDevice = null;
BluetoothPlugin(Executor ioExecutor,
SecureRandom secureRandom,
Backoff backoff, int maxLatency,
DuplexPluginCallback callback) {
super(ioExecutor, secureRandom, backoff, maxLatency, callback);
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
@@ -115,7 +139,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return;
}
if (!running) {
tryToClose((S)ss);
tryToClose(ss);
return;
}
socket = ss;
@@ -129,16 +153,29 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
private String makeUrl(String address, String uuid) {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
}
//
// private void tryToClose(@Nullable StreamConnectionNotifier ss) {
// try {
// if (ss != null) ss.close();
// } catch (IOException e) {
// if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
// } finally {
// callback.transportDisabled();
// }
// }
private String getUuid() {
String uuid = callback.getLocalProperties().get(PROP_UUID);
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
}
return uuid;
}
private void tryToClose(@Nullable StreamConnectionNotifier ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
private void acceptContactConnections(StreamConnectionNotifier ss) {
while (true) {
@@ -159,33 +196,54 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new BluetoothTransportConnection(this, s);
}
//
// @Override
// public void stop() {
// running = false;
// tryToClose(socket);
// }
@Override
protected void close(S ss) throws IOException {
((StreamConnection)ss).close();
public void stop() {
running = false;
tryToClose(socket);
}
@Override
public Runnable returnPollRunnable(final String address, final String uuid,
final ContactId c) {
public boolean isRunning() {
return running;
}
return new Runnable() {
@Override
public void run() {
if (!running) return;
StreamConnection s = connect(makeUrl(address, uuid));
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(final Collection<ContactId> connected) {
if (!running) return;
backoff.increment();
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
final ContactId c = e.getKey();
if (connected.contains(c)) continue;
final String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
StreamConnection s = connect(makeUrl(address, uuid));
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
}
}
};
});
}
}
private StreamConnection connect(String url) {
@@ -201,13 +259,25 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
}
@Override
protected DuplexTransportConnection connectToAddress(String address, String uuid) {
String url = makeUrl(address, uuid);
public DuplexTransportConnection createConnection(ContactId c) {
if (!running) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
String url = makeUrl(address, uuid);
StreamConnection s = connect(url);
if (s == null) return null;
return new BluetoothTransportConnection(this, s);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
@@ -227,7 +297,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null;
}
if (!running) {
tryToClose((S)ss);
tryToClose(ss);
return null;
}
// Create the background tasks
@@ -260,7 +330,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null;
} finally {
// Closing the socket will terminate the listener task
tryToClose((S)ss);
tryToClose(ss);
closeSockets(futures, chosen);
}
}
@@ -292,6 +362,11 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
});
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!running) return null;
@@ -310,7 +385,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null;
}
if (!running) {
tryToClose((S)ss);
tryToClose(ss);
return null;
}
BdfList descriptor = new BdfList();
@@ -320,6 +395,33 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null;
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
String url = makeUrl(address, uuid);
StreamConnection s = connect(url);
if (s == null) return null;
return new BluetoothTransportConnection(this, s);
}
private String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return StringUtils.macToString(mac);
}
private void makeDeviceDiscoverable() {
// Try to make the device discoverable (requires root on Linux)
try {

View File

@@ -5,13 +5,10 @@ dependencies {
def supportVersion = '23.2.1'
compile project(':briar-core')
compile project(':bramble-android')
compile fileTree(dir: 'libs', include: '*.jar')
compile "com.android.support:support-v4:$supportVersion"
compile("com.android.support:appcompat-v7:$supportVersion") {
exclude module: 'support-v4'
}
compile("com.android.support:preference-v14:$supportVersion") {
exclude module: 'support-v4'
}
@@ -20,7 +17,7 @@ dependencies {
exclude module: 'recyclerview-v7'
}
compile "com.android.support:cardview-v7:$supportVersion"
compile 'com.android.support:support-annotations:23.4.0'
compile "com.android.support:support-annotations:$supportVersion"
compile('ch.acra:acra:4.8.5') {
exclude module: 'support-v4'
exclude module: 'support-annotations'
@@ -28,15 +25,16 @@ dependencies {
compile 'info.guardianproject.panic:panic:0.5'
compile 'info.guardianproject.trustedintents:trustedintents:0.2'
compile 'de.hdodenhof:circleimageview:2.1.0'
compile 'com.google.zxing:core:3.2.1'
provided 'javax.annotation:jsr250-api:1.0'
compile 'com.google.zxing:core:3.3.0'
compile 'com.jpardogo.materialtabstrip:library:1.1.0'
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'uk.co.samuelwall:material-tap-target-prompt:1.3.0'
compile 'com.github.bumptech.glide:glide:3.8.0'
compile 'uk.co.samuelwall:material-tap-target-prompt:1.9.2'
provided 'javax.annotation:jsr250-api:1.0'
testCompile project(path: ':bramble-core', configuration: 'testOutput')
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile 'org.mockito:mockito-core:2.8.9'
}
dependencyVerification {
@@ -45,20 +43,18 @@ dependencyVerification {
'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2',
'info.guardianproject.trustedintents:trustedintents:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e',
'de.hdodenhof:circleimageview:bcbc588e19e6dcf8c120b1957776bfe229efba5d2fbe5da7156372eeacf65503',
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
'com.android.support:support-v4:81ce890f26d35c75ad17d0f998a7e3230330c3b41e0b629566bc744bee89e448',
'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d',
'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
'com.github.bumptech.glide:glide:750d9e7b940dc0ee48f8680623b55d46e14e8727acc922d7b156e57e7c549655',
'uk.co.samuelwall:material-tap-target-prompt:5d4951124366bc5c52e57beaa294db7611f0aa2a8d80e0163e1383e1966ba5b2',
'com.android.support:appcompat-v7:00f9d93acacd6731f309724054bf51492814b4b2869f16d7d5c0038dcb8c9a0d',
'com.android.support:preference-v14:44881bb46094e86d0bc2426f205419674a5b4eb514b44b5a4659b5de29f71eb7',
'com.android.support:design:003e0c0bea0a6891f8b2bc43f20ae7af2a49a17363e5bb10df5ee0bae12fa686',
'com.android.support:support-annotations:e91a88dd0c5e99069b7f09d4a46b5e06f1e9c4c72fc0a8e987e25d86af480f01',
'com.android.support:cardview-v7:4595f1c4a28cfa083b6c0920ad4d49e1c2ca4b8302a955e548f68eb63b74931b',
'com.android.support:animated-vector-drawable:06d1963b85aa917099d7757e6a7b3e4dc06889413dc747f625ae8683606db3a1',
'com.android.support:support-vector-drawable:799bafe4c3de812386f0b291f744d5d6876452722dd40189b9ab87dbbf594ea1',
'com.android.support:recyclerview-v7:44040a888e23e0c93162a3377cfe06751080e3c22d369ab0d4301ef60d63b0fe',
'com.android.support:preference-v7:775101bd07bd052e455761c5c5d9523d7ad59f2f320e3e8cbde241fd6b1d6025',
'com.android.support:cardview-v7:4595f1c4a28cfa083b6c0920ad4d49e1c2ca4b8302a955e548f68eb63b74931b',
'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
'com.github.bumptech.glide:glide:76ef123957b5fbaebb05fcbe6606dd58c3bc3fcdadb257f99811d0ac9ea9b88b',
'uk.co.samuelwall:material-tap-target-prompt:f67e1caead12a914525b32cbf6da52a96b93ff89573f93cb41102ef3130fb64a',
]
}
@@ -82,7 +78,10 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
resValue "string", "app_package", "org.briarproject.briar"
versionCode 1605
versionName "0.16.5"
applicationId "org.briarproject.briar.beta"
resValue "string", "app_package", "org.briarproject.briar.beta"
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
}
@@ -107,5 +106,6 @@ android {
lintOptions {
warning 'MissingTranslation'
warning 'ImpliedQuantity'
warning 'ExtraTranslation'
}
}

View File

@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="org.briarproject.briar"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="13"
android:versionName="0.13">
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.bluetooth"/>
<uses-feature android:name="android.hardware.camera" />
@@ -21,7 +19,7 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:name=".android.BriarApplicationImpl"
android:name="org.briarproject.briar.android.BriarApplicationImpl"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
@@ -29,7 +27,7 @@
android:theme="@style/BriarTheme">
<service
android:name=".android.BriarService"
android:name="org.briarproject.briar.android.BriarService"
android:exported="false">
<intent-filter>
<action android:name="org.briarproject.briar.android.BriarService"/>
@@ -37,7 +35,7 @@
</service>
<activity
android:name=".android.reporting.DevReportActivity"
android:name="org.briarproject.briar.android.reporting.DevReportActivity"
android:excludeFromRecents="true"
android:exported="false"
android:finishOnTaskLaunch="true"
@@ -49,24 +47,24 @@
</activity>
<activity
android:name=".android.splash.ExpiredActivity"
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
android:label="@string/app_name">
</activity>
<activity
android:name=".android.login.PasswordActivity"
android:name="org.briarproject.briar.android.login.PasswordActivity"
android:label="@string/app_name"
android:windowSoftInputMode="stateVisible">
</activity>
<activity
android:name=".android.login.SetupActivity"
android:name="org.briarproject.briar.android.login.SetupActivity"
android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize">
</activity>
<activity
android:name=".android.splash.SplashScreenActivity"
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
android:theme="@style/BriarTheme.NoActionBar"
android:label="@string/app_name">
<intent-filter>
@@ -76,268 +74,268 @@
</activity>
<activity
android:name=".android.navdrawer.NavDrawerActivity"
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar"
android:launchMode="singleTop">
</activity>
<activity
android:name=".android.contact.ConversationActivity"
android:name="org.briarproject.briar.android.contact.ConversationActivity"
android:label="@string/app_name"
android:theme="@style/BriarTheme.NoActionBar"
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:windowSoftInputMode="stateHidden|adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.privategroup.creation.CreateGroupActivity"
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
android:label="@string/groups_create_group_title"
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.privategroup.conversation.GroupActivity"
android:name="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
android:label="@string/app_name"
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.privategroup.invitation.GroupInvitationActivity"
android:name="org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity"
android:label="@string/groups_invitations_title"
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
android:name=".android.privategroup.memberlist.GroupMemberListActivity"
android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity"
android:label="@string/groups_member_list"
android:parentActivityName=".android.privategroup.conversation.GroupActivity"
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.privategroup.conversation.GroupActivity"
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
/>
</activity>
<activity
android:name=".android.privategroup.reveal.RevealContactsActivity"
android:name="org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity"
android:label="@string/groups_reveal_contacts"
android:parentActivityName=".android.privategroup.conversation.GroupActivity"
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.privategroup.conversation.GroupActivity"
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
/>
</activity>
<activity
android:name=".android.privategroup.creation.GroupInviteActivity"
android:name="org.briarproject.briar.android.privategroup.creation.GroupInviteActivity"
android:label="@string/groups_invite_members"
android:parentActivityName=".android.privategroup.conversation.GroupActivity"
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.privategroup.conversation.GroupActivity"/>
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
</activity>
<activity
android:name=".android.sharing.ForumInvitationActivity"
android:name="org.briarproject.briar.android.sharing.ForumInvitationActivity"
android:label="@string/forum_invitations_title"
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.sharing.BlogInvitationActivity"
android:name="org.briarproject.briar.android.sharing.BlogInvitationActivity"
android:label="@string/blogs_sharing_invitations_title"
android:parentActivityName=".android.contact.ConversationActivity">
android:parentActivityName="org.briarproject.briar.android.contact.ConversationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.contact.ConversationActivity"
android:value="org.briarproject.briar.android.contact.ConversationActivity"
/>
</activity>
<activity
android:name=".android.forum.CreateForumActivity"
android:name="org.briarproject.briar.android.forum.CreateForumActivity"
android:label="@string/create_forum_title"
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:windowSoftInputMode="stateVisible">
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.forum.ForumActivity"
android:name="org.briarproject.briar.android.forum.ForumActivity"
android:label="@string/app_name"
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.sharing.ShareForumActivity"
android:name="org.briarproject.briar.android.sharing.ShareForumActivity"
android:label="@string/activity_share_toolbar_header"
android:parentActivityName=".android.forum.ForumActivity"
android:parentActivityName="org.briarproject.briar.android.forum.ForumActivity"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.forum.ForumActivity"
android:value="org.briarproject.briar.android.forum.ForumActivity"
/>
</activity>
<activity
android:name=".android.sharing.ShareBlogActivity"
android:name="org.briarproject.briar.android.sharing.ShareBlogActivity"
android:label="@string/activity_share_toolbar_header"
android:parentActivityName=".android.blog.BlogActivity"
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.blog.BlogActivity"
android:value="org.briarproject.briar.android.blog.BlogActivity"
/>
</activity>
<activity
android:name=".android.sharing.ForumSharingStatusActivity"
android:name="org.briarproject.briar.android.sharing.ForumSharingStatusActivity"
android:label="@string/sharing_status"
android:parentActivityName=".android.forum.ForumActivity">
android:parentActivityName="org.briarproject.briar.android.forum.ForumActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.forum.ForumActivity"
android:value="org.briarproject.briar.android.forum.ForumActivity"
/>
</activity>
<activity
android:name=".android.sharing.BlogSharingStatusActivity"
android:name="org.briarproject.briar.android.sharing.BlogSharingStatusActivity"
android:label="@string/sharing_status"
android:parentActivityName=".android.blog.BlogActivity">
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.blog.BlogActivity"
android:value="org.briarproject.briar.android.blog.BlogActivity"
/>
</activity>
<activity
android:name=".android.blog.BlogActivity"
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:name="org.briarproject.briar.android.blog.BlogActivity"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
android:name=".android.blog.WriteBlogPostActivity"
android:name="org.briarproject.briar.android.blog.WriteBlogPostActivity"
android:label="@string/blogs_write_blog_post"
android:parentActivityName=".android.blog.BlogActivity"
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
android:windowSoftInputMode="stateVisible|adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.blog.BlogActivity"
android:value="org.briarproject.briar.android.blog.BlogActivity"
/>
</activity>
<activity
android:name=".android.blog.ReblogActivity"
android:name="org.briarproject.briar.android.blog.ReblogActivity"
android:label="@string/blogs_reblog_button"
android:parentActivityName=".android.blog.BlogActivity"
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity"
android:windowSoftInputMode="stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.blog.BlogActivity"
android:value="org.briarproject.briar.android.blog.BlogActivity"
/>
</activity>
<activity
android:name=".android.blog.RssFeedImportActivity"
android:name="org.briarproject.briar.android.blog.RssFeedImportActivity"
android:label="@string/blogs_rss_feeds_import"
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:windowSoftInputMode="stateVisible|adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.blog.RssFeedManageActivity"
android:name="org.briarproject.briar.android.blog.RssFeedManageActivity"
android:label="@string/blogs_rss_feeds_manage"
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.invitation.AddContactActivity"
android:name="org.briarproject.briar.android.invitation.AddContactActivity"
android:label="@string/add_contact_title"
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.keyagreement.KeyAgreementActivity"
android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
android:label="@string/add_contact_title"
android:theme="@style/BriarTheme.NoActionBar"
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
android:name=".android.introduction.IntroductionActivity"
android:name="org.briarproject.briar.android.introduction.IntroductionActivity"
android:label="@string/introduction_activity_title"
android:parentActivityName=".android.contact.ConversationActivity"
android:parentActivityName="org.briarproject.briar.android.contact.ConversationActivity"
android:windowSoftInputMode="stateHidden|adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.contact.ConversationActivity"
android:value="org.briarproject.briar.android.contact.ConversationActivity"
/>
</activity>
<activity
android:name=".android.StartupFailureActivity"
android:name="org.briarproject.briar.android.StartupFailureActivity"
android:label="@string/startup_failed_activity_title">
</activity>
<activity
android:name=".android.settings.SettingsActivity"
android:name="org.briarproject.briar.android.settings.SettingsActivity"
android:label="@string/settings_button"
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:permission="android.permission.READ_NETWORK_USAGE_HISTORY">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
/>
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE"/>
@@ -346,27 +344,27 @@
</activity>
<activity
android:name=".android.login.ChangePasswordActivity"
android:name="org.briarproject.briar.android.login.ChangePasswordActivity"
android:label="@string/change_password"
android:parentActivityName=".android.settings.SettingsActivity">
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.settings.SettingsActivity"
android:value="org.briarproject.briar.android.settings.SettingsActivity"
/>
</activity>
<activity
android:name=".android.panic.PanicPreferencesActivity"
android:name="org.briarproject.briar.android.panic.PanicPreferencesActivity"
android:label="@string/panic_setting"
android:parentActivityName=".android.settings.SettingsActivity">
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.settings.SettingsActivity"
android:value="org.briarproject.briar.android.settings.SettingsActivity"
/>
</activity>
<activity
android:name=".android.panic.PanicResponderActivity"
android:name="org.briarproject.briar.android.panic.PanicResponderActivity"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
<!-- this can never have launchMode singleTask or singleInstance! -->
@@ -377,7 +375,7 @@
</activity>
<activity
android:name=".android.panic.ExitActivity"
android:name="org.briarproject.briar.android.panic.ExitActivity"
android:theme="@android:style/Theme.NoDisplay">
</activity>

View File

@@ -32,6 +32,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
@@ -78,6 +79,8 @@ public interface AndroidComponent
@DatabaseExecutor
Executor databaseExecutor();
MessageTracker messageTracker();
LifecycleManager lifecycleManager();
IdentityManager identityManager();

View File

@@ -61,6 +61,7 @@ import static android.content.Context.NOTIFICATION_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
import static android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@@ -327,7 +328,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_MESSAGE);
b.setVisibility(VISIBILITY_SECRET);
boolean showOnLockScreen =
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
if (showOnLockScreen)
b.setVisibility(VISIBILITY_PRIVATE);
else
b.setVisibility(VISIBILITY_SECRET);
}
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
@@ -347,17 +353,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
return defaults;
}
@Override
public void clearAllContactNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
clearContactNotification();
clearIntroductionSuccessNotification();
}
});
}
@UiThread
private void showGroupMessageNotification(final GroupId g) {
androidExecutor.runOnUiThread(new Runnable() {
@@ -432,7 +427,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_SOCIAL);
b.setVisibility(VISIBILITY_SECRET);
boolean showOnLockScreen =
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
if (showOnLockScreen)
b.setVisibility(VISIBILITY_PRIVATE);
else
b.setVisibility(VISIBILITY_SECRET);
}
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
@@ -440,16 +440,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
}
@Override
public void clearAllGroupMessageNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
clearGroupMessageNotification();
}
});
}
@UiThread
private void showForumPostNotification(final GroupId g) {
androidExecutor.runOnUiThread(new Runnable() {
@@ -524,7 +514,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_SOCIAL);
b.setVisibility(VISIBILITY_SECRET);
boolean showOnLockScreen =
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
if (showOnLockScreen)
b.setVisibility(VISIBILITY_PRIVATE);
else
b.setVisibility(VISIBILITY_SECRET);
}
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
@@ -532,16 +527,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
}
@Override
public void clearAllForumPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
clearForumPostNotification();
}
});
}
@UiThread
private void showBlogPostNotification(final GroupId g) {
androidExecutor.runOnUiThread(new Runnable() {
@@ -602,7 +587,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_SOCIAL);
b.setVisibility(VISIBILITY_SECRET);
boolean showOnLockScreen =
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
if (showOnLockScreen)
b.setVisibility(VISIBILITY_PRIVATE);
else
b.setVisibility(VISIBILITY_SECRET);
}
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
@@ -658,7 +648,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_MESSAGE);
b.setVisibility(VISIBILITY_SECRET);
boolean showOnLockScreen =
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
if (showOnLockScreen)
b.setVisibility(VISIBILITY_PRIVATE);
else
b.setVisibility(VISIBILITY_SECRET);
}
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
@@ -705,68 +700,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
@Override
public void blockAllContactNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockContacts = true;
blockIntroductions = true;
}
});
}
@Override
public void unblockAllContactNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockContacts = false;
blockIntroductions = false;
}
});
}
@Override
public void blockAllGroupMessageNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockGroups = true;
}
});
}
@Override
public void unblockAllGroupMessageNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockGroups = false;
}
});
}
@Override
public void blockAllForumPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockForums = true;
}
});
}
@Override
public void unblockAllForumPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockForums = false;
}
});
}
@Override
public void blockAllBlogPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@@ -786,5 +719,4 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
});
}
}

View File

@@ -38,8 +38,6 @@ public class AppModule {
static class EagerSingletons {
@Inject
AndroidNotificationManager androidNotificationManager;
@Inject
ScreenFilterMonitor screenFilterMonitor;
}
private final Application application;
@@ -171,10 +169,8 @@ public class AppModule {
}
@Provides
@Singleton
ScreenFilterMonitor provideScreenFilterMonitor(
LifecycleManager lifecycleManager, ScreenFilterMonitorImpl sfm) {
lifecycleManager.registerService(sfm);
return sfm;
ScreenFilterMonitorImpl screenFilterMonitor) {
return screenFilterMonitor;
}
}

View File

@@ -6,5 +6,9 @@ package org.briarproject.briar.android;
*/
public interface BriarApplication {
// This build expires on 21 October 2017
long EXPIRY_DATE = 1508544000 * 1000L;
AndroidComponent getApplicationComponent();
}

View File

@@ -2,6 +2,9 @@ package org.briarproject.briar.android;
import android.app.Application;
import android.content.Context;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.os.StrictMode.VmPolicy;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
@@ -33,6 +36,8 @@ import static org.acra.ReportField.REPORT_ID;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_APP_START_DATE;
import static org.acra.ReportField.USER_CRASH_DATE;
import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@ReportsCrashes(
reportPrimerClass = BriarReportPrimer.class,
@@ -72,6 +77,9 @@ public class BriarApplicationImpl extends Application
@Override
public void onCreate() {
super.onCreate();
if (IS_DEBUG_BUILD) enableStrictMode();
Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
LOG.info("Created");
applicationComponent = DaggerAndroidComponent.builder()
@@ -85,6 +93,17 @@ public class BriarApplicationImpl extends Application
AndroidEagerSingletons.initEagerSingletons(applicationComponent);
}
private void enableStrictMode() {
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
threadPolicy.detectAll();
threadPolicy.penaltyLog();
StrictMode.setThreadPolicy(threadPolicy.build());
VmPolicy.Builder vmPolicy = new VmPolicy.Builder();
vmPolicy.detectAll();
vmPolicy.penaltyLog();
StrictMode.setVmPolicy(vmPolicy.build());
}
@Override
public AndroidComponent getApplicationComponent() {
return applicationComponent;

View File

@@ -1,24 +1,13 @@
package org.briarproject.briar.android;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v7.preference.PreferenceManager;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
@@ -27,37 +16,26 @@ import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.GET_SIGNATURES;
import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ScreenFilterMonitorImpl extends BroadcastReceiver
implements Service, ScreenFilterMonitor {
@NotNullByDefault
class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
private static final Logger LOG =
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
private static final String PREF_SCREEN_FILTER_APPS =
"shownScreenFilterApps";
/*
* Ignore Play Services if it uses this package name and public key - it's
@@ -78,124 +56,17 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
"0B145B6AA192858E79020103";
private final Context appContext;
private final AndroidExecutor androidExecutor;
private final PackageManager pm;
private final SharedPreferences prefs;
private final AtomicBoolean used = new AtomicBoolean(false);
// The following must only be accessed on the UI thread
private final Set<String> apps = new HashSet<>();
private final Set<String> shownApps;
private boolean serviceStarted = false;
@Inject
ScreenFilterMonitorImpl(AndroidExecutor executor, Application app) {
this.androidExecutor = executor;
this.appContext = app;
pm = appContext.getPackageManager();
prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
shownApps = getShownScreenFilterApps();
}
@Override
public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException();
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
@Override
public Void call() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_PACKAGE_ADDED);
intentFilter.addDataScheme("package");
appContext.registerReceiver(ScreenFilterMonitorImpl.this,
intentFilter);
apps.addAll(getInstalledScreenFilterApps());
serviceStarted = true;
return null;
}
});
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
throw new ServiceException(e);
}
}
@Override
public void stopService() throws ServiceException {
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
@Override
public Void call() {
serviceStarted = false;
appContext.unregisterReceiver(ScreenFilterMonitorImpl.this);
return null;
}
});
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
throw new ServiceException(e);
}
}
private Set<String> getShownScreenFilterApps() {
// Result must not be modified
Set<String> s = prefs.getStringSet(PREF_SCREEN_FILTER_APPS, null);
HashSet<String> result = new HashSet<>();
if (s != null) {
result.addAll(s);
}
return result;
}
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
final String packageName =
intent.getData().getEncodedSchemeSpecificPart();
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
String pkg = isOverlayApp(packageName);
if (pkg == null) {
return;
}
apps.add(pkg);
}
});
}
ScreenFilterMonitorImpl(Application app) {
pm = app.getPackageManager();
}
@Override
@UiThread
public Set<String> getApps() {
if (!serviceStarted) {
apps.addAll(getInstalledScreenFilterApps());
}
TreeSet<String> buf = new TreeSet<>();
if (apps.isEmpty()) {
return buf;
}
buf.addAll(apps);
buf.removeAll(shownApps);
return buf;
}
@Override
@UiThread
public void storeAppsAsShown(Collection<String> s, boolean persistent) {
HashSet<String> buf = new HashSet<>(s);
shownApps.addAll(buf);
if (persistent && !s.isEmpty()) {
buf.addAll(getShownScreenFilterApps());
prefs.edit()
.putStringSet(PREF_SCREEN_FILTER_APPS, buf)
.apply();
}
}
private Set<String> getInstalledScreenFilterApps() {
HashSet<String> screenFilterApps = new HashSet<>();
Set<String> screenFilterApps = new TreeSet<>();
List<PackageInfo> packageInfos =
pm.getInstalledPackages(GET_PERMISSIONS);
for (PackageInfo packageInfo : packageInfos) {
@@ -209,21 +80,6 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
return screenFilterApps;
}
// Checks if a package uses the SYSTEM_ALERT_WINDOW permission and if so
// returns the app name.
@Nullable
private String isOverlayApp(String pkg) {
try {
PackageInfo pkgInfo = pm.getPackageInfo(pkg, GET_PERMISSIONS);
if (isOverlayApp(pkgInfo)) {
return pkgToString(pkgInfo);
}
} catch (NameNotFoundException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
return null;
}
// Fetches the application name for a given package.
@Nullable
private String pkgToString(PackageInfo pkgInfo) {

View File

@@ -10,13 +10,21 @@ import static java.util.logging.Level.OFF;
public interface TestingConstants {
/**
* Whether this is an alpha or beta build. This should be set to false for
* Whether this is a debug build.
*/
boolean IS_DEBUG_BUILD = BuildConfig.DEBUG;
/**
* Whether this is a beta build. This should be set to false for final
* release builds.
*/
boolean TESTING = BuildConfig.DEBUG;
boolean IS_BETA_BUILD = true;
/** Default log level. */
Level DEFAULT_LOG_LEVEL = TESTING ? INFO : OFF;
/**
* Default log level. Disable logging for final release builds.
*/
@SuppressWarnings("ConstantConditions")
Level DEFAULT_LOG_LEVEL = IS_DEBUG_BUILD || IS_BETA_BUILD ? INFO : OFF;
/**
* Whether to prevent screenshots from being taken. Setting this to true
@@ -24,5 +32,5 @@ public interface TestingConstants {
* Unfortunately this also prevents the user from taking screenshots
* intentionally.
*/
boolean PREVENT_SCREENSHOTS = !TESTING;
boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD;
}

View File

@@ -39,7 +39,7 @@ import org.briarproject.briar.android.privategroup.conversation.GroupConversatio
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
import org.briarproject.briar.android.privategroup.creation.CreateGroupMessageFragment;
import org.briarproject.briar.android.privategroup.creation.GroupCreateModule;
import org.briarproject.briar.android.privategroup.creation.CreateGroupModule;
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
@@ -71,7 +71,7 @@ import dagger.Component;
@Component(
modules = {ActivityModule.class, ForumModule.class, SharingModule.class,
BlogModule.class, ContactModule.class, GroupListModule.class,
GroupCreateModule.class, GroupInvitationModule.class,
CreateGroupModule.class, GroupInvitationModule.class,
GroupConversationModule.class, GroupMemberModule.class,
GroupRevealModule.class},
dependencies = AndroidComponent.class)

View File

@@ -2,9 +2,13 @@ package org.briarproject.briar.android.activity;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.LayoutRes;
import android.support.annotation.UiThread;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import org.briarproject.bramble.api.db.DbException;
@@ -13,7 +17,9 @@ import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.SFDialogFragment;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import java.util.ArrayList;
@@ -23,21 +29,23 @@ import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
public abstract class BaseActivity extends AppCompatActivity
implements DestroyableContext {
implements DestroyableContext, OnTapFilteredListener {
@Inject
protected ScreenFilterMonitor screenFilterMonitor;
protected ActivityComponent activityComponent;
private final List<ActivityLifecycleController> lifecycleControllers =
new ArrayList<>();
private boolean destroyed = false;
@Inject
protected ScreenFilterMonitor screenFilterMonitor;
private SFDialogFragment dialogFrag;
private ScreenFilterDialogFragment dialogFrag;
public abstract void injectActivity(ActivityComponent component);
@@ -65,7 +73,6 @@ public abstract class BaseActivity extends AppCompatActivity
for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityCreate(this);
}
}
public ActivityComponent getActivityComponent() {
@@ -97,12 +104,6 @@ public abstract class BaseActivity extends AppCompatActivity
}
}
@Override
protected void onPostResume() {
super.onPostResume();
showNewScreenFilterWarning();
}
@Override
protected void onPause() {
super.onPause();
@@ -112,18 +113,14 @@ public abstract class BaseActivity extends AppCompatActivity
}
}
protected void showNewScreenFilterWarning() {
final Set<String> apps = screenFilterMonitor.getApps();
if (apps.isEmpty()) {
return;
}
dialogFrag = SFDialogFragment.newInstance(new ArrayList<>(apps));
private void showScreenFilterWarning() {
if (dialogFrag != null && dialogFrag.isVisible()) return;
Set<String> apps = screenFilterMonitor.getApps();
if (apps.isEmpty()) return;
dialogFrag =
ScreenFilterDialogFragment.newInstance(new ArrayList<>(apps));
dialogFrag.setCancelable(false);
dialogFrag.show(getSupportFragmentManager(), "SFDialog");
}
public void rememberShownApps(ArrayList<String> s, boolean permanent) {
screenFilterMonitor.storeAppsAsShown(s, permanent);
dialogFrag.show(getSupportFragmentManager(), dialogFrag.getTag());
}
@Override
@@ -161,4 +158,70 @@ public abstract class BaseActivity extends AppCompatActivity
supportFinishAfterTransition();
}
/*
* Wraps the given view in a wrapper that notifies this activity when an
* obscured touch has been filtered, and returns the wrapper.
*/
private View makeTapSafeWrapper(View v) {
TapSafeFrameLayout wrapper = new TapSafeFrameLayout(this);
wrapper.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
wrapper.setOnTapFilteredListener(this);
wrapper.addView(v);
return wrapper;
}
/*
* Finds the AppCompat toolbar, if any, and configures it to filter
* obscured touches. If a custom toolbar is used, it will be part of the
* content view and thus protected by the wrapper. But the default toolbar
* is outside the wrapper.
*/
private void protectToolbar() {
View decorView = getWindow().getDecorView();
if (decorView instanceof ViewGroup) {
Toolbar toolbar = findToolbar((ViewGroup) decorView);
if (toolbar != null) toolbar.setFilterTouchesWhenObscured(true);
}
}
@Nullable
private Toolbar findToolbar(ViewGroup vg) {
for (int i = 0, len = vg.getChildCount(); i < len; i++) {
View child = vg.getChildAt(i);
if (child instanceof Toolbar) return (Toolbar) child;
if (child instanceof ViewGroup) {
Toolbar toolbar = findToolbar((ViewGroup) child);
if (toolbar != null) return toolbar;
}
}
return null;
}
@Override
public void setContentView(@LayoutRes int layoutRes) {
setContentView(getLayoutInflater().inflate(layoutRes, null));
}
@Override
public void setContentView(View v) {
super.setContentView(makeTapSafeWrapper(v));
protectToolbar();
}
@Override
public void setContentView(View v, LayoutParams layoutParams) {
super.setContentView(makeTapSafeWrapper(v), layoutParams);
protectToolbar();
}
@Override
public void addContentView(View v, LayoutParams layoutParams) {
super.addContentView(makeTapSafeWrapper(v), layoutParams);
protectToolbar();
}
@Override
public void onTapFiltered() {
showScreenFilterWarning();
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.transition.Slide;
@@ -21,6 +20,7 @@ import org.briarproject.briar.android.panic.ExitActivity;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -20,8 +21,10 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
@UiThread
@@ -57,7 +60,20 @@ abstract class BasePostFragment extends BaseFragment {
false);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view);
ui = new BlogPostViewHolder(view, true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// We're already there
}
@Override
public void onAuthorClick(BlogPostItem post) {
Intent i = new Intent(getContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(i);
}
});
return view;
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar;
import android.view.View;
@@ -15,6 +14,7 @@ import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
import javax.annotation.Nullable;
import javax.inject.Inject;
@MethodsNotNullByDefault

View File

@@ -106,7 +106,7 @@ class BlogControllerImpl extends BaseControllerImpl
BlogInvitationResponseReceivedEvent b =
(BlogInvitationResponseReceivedEvent) e;
InvitationResponse r = b.getResponse();
if (r.getGroupId().equals(groupId) && r.wasAccepted()) {
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
LOG.info("Blog invitation accepted");
onBlogInvitationAccepted(b.getContactId());
}
@@ -169,7 +169,7 @@ class BlogControllerImpl extends BaseControllerImpl
LocalAuthor a = identityManager.getLocalAuthor();
Blog b = blogManager.getBlog(groupId);
boolean ours = a.getId().equals(b.getAuthor().getId());
boolean removable = blogManager.canBeRemoved(groupId);
boolean removable = blogManager.canBeRemoved(b);
BlogItem blog = new BlogItem(b, ours, removable);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))

View File

@@ -27,7 +27,6 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.BlogController.BlogSharingListener;
import org.briarproject.briar.android.blog.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.briar.android.controller.SharingController;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.fragment.BaseFragment;
@@ -216,10 +215,19 @@ public class BlogFragment extends BaseFragment
showNextFragment(f);
}
@Override
public void onAuthorClick(BlogPostItem post) {
if (post.getGroupId().equals(groupId)) return; // We're already there
Intent i = new Intent(getContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(i);
}
private void loadBlogPosts(final boolean reload) {
blogController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
this) {
new UiResultExceptionHandler<Collection<BlogPostItem>,
DbException>(this) {
@Override
public void onResultUi(Collection<BlogPostItem> posts) {
if (posts.isEmpty()) {
@@ -257,13 +265,13 @@ public class BlogFragment extends BaseFragment
}
private void setToolbarTitle(Author a) {
String title = getString(R.string.blogs_personal_blog, a.getName());
getActivity().setTitle(title);
getActivity().setTitle(a.getName());
}
private void loadSharedContacts() {
blogController.loadSharingContacts(
new UiResultExceptionHandler<Collection<ContactId>, DbException>(this) {
new UiResultExceptionHandler<Collection<ContactId>,
DbException>(this) {
@Override
public void onResultUi(Collection<ContactId> contacts) {
sharingController.addAll(contacts);

View File

@@ -23,8 +23,7 @@ class BlogPostAdapter
int viewType) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_blog_post, parent, false);
BlogPostViewHolder ui = new BlogPostViewHolder(v);
ui.setOnBlogPostClickListener(listener);
BlogPostViewHolder ui = new BlogPostViewHolder(v, false, listener);
return ui;
}
@@ -48,8 +47,4 @@ class BlogPostAdapter
return a.getId().equals(b.getId());
}
interface OnBlogPostClickListener {
void onBlogPostClick(BlogPostItem post);
}
}

View File

@@ -8,14 +8,16 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.BaseController.BlogListener;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.api.blog.BlogPostHeader;
import javax.inject.Inject;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BlogPostFragment extends BasePostFragment {
public class BlogPostFragment extends BasePostFragment implements BlogListener {
private static final String TAG = BlogPostFragment.class.getName();
@@ -40,6 +42,7 @@ public class BlogPostFragment extends BasePostFragment {
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
blogController.setBlogListener(this);
}
@Override
@@ -59,4 +62,15 @@ public class BlogPostFragment extends BasePostFragment {
}
});
}
@Override
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
// doesn't matter here
}
@Override
public void onBlogRemoved() {
finish();
}
}

View File

@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
@@ -20,7 +21,6 @@ import android.widget.TextView;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.blog.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.briar.android.view.AuthorView;
import org.briarproject.briar.api.blog.BlogCommentHeader;
import org.briarproject.briar.api.blog.BlogPostHeader;
@@ -48,11 +48,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
private final ImageView reblogButton;
private final TextView body;
private final ViewGroup commentContainer;
private final boolean fullText;
private OnBlogPostClickListener listener;
@NonNull
private final OnBlogPostClickListener listener;
BlogPostViewHolder(View v) {
BlogPostViewHolder(View v, boolean fullText,
@NonNull OnBlogPostClickListener listener) {
super(v);
this.fullText = fullText;
this.listener = listener;
ctx = v.getContext();
layout = (ViewGroup) v.findViewById(R.id.postLayout);
@@ -64,10 +69,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
(ViewGroup) v.findViewById(R.id.commentContainer);
}
void setOnBlogPostClickListener(OnBlogPostClickListener listener) {
this.listener = listener;
}
void setVisibility(int visibility) {
layout.setVisibility(visibility);
}
@@ -92,7 +93,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
if (item == null) return;
setTransitionName(item.getId());
if (listener != null) {
if (!fullText) {
layout.setClickable(true);
layout.setOnClickListener(new OnClickListener() {
@Override
@@ -111,15 +112,20 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
author.setPersona(
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
// TODO make author clickable more often #624
if (item.getHeader().getType() == POST) {
author.setBlogLink(post.getGroupId());
if (!fullText && item.getHeader().getType() == POST) {
author.setAuthorClickable(new OnClickListener() {
@Override
public void onClick(View v) {
listener.onAuthorClick(item);
}
});
} else {
author.unsetBlogLink();
author.setAuthorNotClickable();
}
// post body
Spanned bodyText = getSpanned(item.getBody());
if (listener == null) {
if (fullText) {
body.setText(bodyText);
body.setTextIsSelectable(true);
makeLinksClickable(body);
@@ -165,7 +171,14 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
reblogger.setAuthor(item.getAuthor());
reblogger.setAuthorStatus(item.getAuthorStatus());
reblogger.setDate(item.getTimestamp());
reblogger.setBlogLink(item.getGroupId());
if (!fullText) {
reblogger.setAuthorClickable(new OnClickListener() {
@Override
public void onClick(View v) {
listener.onAuthorClick(item);
}
});
}
reblogger.setVisibility(VISIBLE);
reblogger.setPersona(AuthorView.REBLOGGER);
@@ -188,7 +201,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// TODO make author clickable #624
body.setText(c.getComment());
if (listener == null) body.setTextIsSelectable(true);
if (fullText) body.setTextIsSelectable(true);
commentContainer.addView(v);
}

View File

@@ -19,7 +19,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.briar.android.blog.FeedController.FeedListener;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.fragment.BaseFragment;
@@ -34,6 +33,7 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
@@ -223,6 +223,14 @@ public class FeedFragment extends BaseFragment implements
showNextFragment(f);
}
@Override
public void onAuthorClick(BlogPostItem post) {
Intent i = new Intent(getContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(i);
}
@Override
public String getUniqueTag() {
return TAG;

View File

@@ -0,0 +1,8 @@
package org.briarproject.briar.android.blog;
interface OnBlogPostClickListener {
void onBlogPostClick(BlogPostItem post);
void onAuthorClick(BlogPostItem post);
}

View File

@@ -161,7 +161,18 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
private ViewHolder(View v) {
scrollView = (ScrollView) v.findViewById(R.id.scrollView);
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout));
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
});
input = (TextInputView) v.findViewById(R.id.inputText);
}
}

View File

@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -20,9 +21,12 @@ import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.api.feed.FeedManager;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.view.View.GONE;
@@ -98,10 +102,17 @@ public class RssFeedImportActivity extends BriarActivity {
private void enableOrDisableImportButton() {
String url = urlInput.getText().toString();
if (url.startsWith("http://") || url.startsWith("https://"))
importButton.setEnabled(true);
else
importButton.setEnabled(false);
importButton.setEnabled(validateAndNormaliseUrl(url) != null);
}
@Nullable
private String validateAndNormaliseUrl(String url) {
if (!Patterns.WEB_URL.matcher(url).matches()) return null;
try {
return new URL(url).toString();
} catch (MalformedURLException e) {
return null;
}
}
private void publish() {
@@ -109,7 +120,9 @@ public class RssFeedImportActivity extends BriarActivity {
importButton.setVisibility(GONE);
progressBar.setVisibility(VISIBLE);
importFeed(urlInput.getText().toString());
String url = validateAndNormaliseUrl(urlInput.getText().toString());
if (url == null) throw new AssertionError();
importFeed(url);
}
private void importFeed(final String url) {

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.contact;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v7.widget.RecyclerView;
import android.view.View;
@@ -13,6 +12,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import javax.annotation.Nullable;
import im.delight.android.identicons.IdenticonDrawable;
@UiThread

View File

@@ -178,8 +178,6 @@ public class ContactListFragment extends BaseFragment implements EventListener {
@Override
public void onStart() {
super.onStart();
notificationManager.blockAllContactNotifications();
notificationManager.clearAllContactNotifications();
eventBus.addListener(this);
loadContacts();
list.startPeriodicUpdate();
@@ -189,7 +187,6 @@ public class ContactListFragment extends BaseFragment implements EventListener {
public void onStop() {
super.onStop();
eventBus.removeListener(this);
notificationManager.unblockAllContactNotifications();
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate();

View File

@@ -17,7 +17,7 @@ public class DbControllerImpl implements DbController {
private static final Logger LOG =
Logger.getLogger(DbControllerImpl.class.getName());
private final Executor dbExecutor;
protected final Executor dbExecutor;
private final LifecycleManager lifecycleManager;
@Inject

View File

@@ -1,7 +1,5 @@
package org.briarproject.briar.android.controller;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
@@ -15,6 +13,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
@NotNullByDefault

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
@@ -38,16 +39,15 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LEN
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class CreateForumActivity extends BriarActivity
implements OnEditorActionListener, OnClickListener {
public class CreateForumActivity extends BriarActivity {
private static final Logger LOG =
Logger.getLogger(CreateForumActivity.class.getName());
private TextInputLayout nameEntryLayout;
private EditText nameEntry;
private Button createForumButton;
private ProgressBar progress;
private TextView feedback;
// Fields that are accessed from background threads must be volatile
@Inject
@@ -59,12 +59,10 @@ public class CreateForumActivity extends BriarActivity
setContentView(R.layout.activity_create_forum);
nameEntryLayout =
(TextInputLayout) findViewById(R.id.createForumNameLayout);
nameEntry = (EditText) findViewById(R.id.createForumNameEntry);
TextWatcher nameEntryWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
nameEntry.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
@@ -72,21 +70,39 @@ public class CreateForumActivity extends BriarActivity
}
@Override
public void onTextChanged(CharSequence text, int start,
public void onTextChanged(CharSequence s, int start,
int lengthBefore, int lengthAfter) {
enableOrDisableCreateButton();
}
};
nameEntry.setOnEditorActionListener(this);
nameEntry.addTextChangedListener(nameEntryWatcher);
feedback = (TextView) findViewById(R.id.createForumFeedback);
@Override
public void afterTextChanged(Editable s) {
}
});
nameEntry.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent e) {
createForum();
return true;
}
});
createForumButton = (Button) findViewById(R.id.createForumButton);
createForumButton.setOnClickListener(this);
createForumButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
createForum();
}
});
progress = (ProgressBar) findViewById(R.id.createForumProgressBar);
}
@Override
public void onStart() {
super.onStart();
showSoftKeyboard(nameEntry);
}
@Override
@@ -95,36 +111,27 @@ public class CreateForumActivity extends BriarActivity
}
private void enableOrDisableCreateButton() {
if (progress == null) return; // Not created yet
if (createForumButton == null) return; // Not created yet
createForumButton.setEnabled(validateName());
}
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
hideSoftKeyboard(textView);
return true;
}
private boolean validateName() {
String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length;
if (length > MAX_FORUM_NAME_LENGTH) {
feedback.setText(R.string.name_too_long);
nameEntryLayout.setError(getString(R.string.name_too_long));
return false;
}
feedback.setText("");
nameEntryLayout.setError(null);
return length > 0;
}
@Override
public void onClick(View view) {
if (view == createForumButton) {
hideSoftKeyboard(view);
if (!validateName()) return;
createForumButton.setVisibility(GONE);
progress.setVisibility(VISIBLE);
storeForum(nameEntry.getText().toString());
}
private void createForum() {
if (!validateName()) return;
hideSoftKeyboard(nameEntry);
createForumButton.setVisibility(GONE);
progress.setVisibility(VISIBLE);
storeForum(nameEntry.getText().toString());
}
private void storeForum(final String name) {

View File

@@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.android.forum.ForumController.ForumListener;
import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
@@ -55,10 +56,10 @@ class ForumControllerImpl extends
LifecycleManager lifecycleManager, IdentityManager identityManager,
@CryptoExecutor Executor cryptoExecutor,
ForumManager forumManager, ForumSharingManager forumSharingManager,
EventBus eventBus, Clock clock,
EventBus eventBus, Clock clock, MessageTracker messageTracker,
AndroidNotificationManager notificationManager) {
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
eventBus, clock, notificationManager);
eventBus, clock, notificationManager, messageTracker);
this.forumManager = forumManager;
this.forumSharingManager = forumSharingManager;
}
@@ -84,7 +85,7 @@ class ForumControllerImpl extends
(ForumInvitationResponseReceivedEvent) e;
ForumInvitationResponse r =
(ForumInvitationResponse) f.getResponse();
if (r.getGroupId().equals(getGroupId()) && r.wasAccepted()) {
if (r.getShareableId().equals(getGroupId()) && r.wasAccepted()) {
LOG.info("Forum invitation was accepted");
onForumInvitationAccepted(r.getContactId());
}

View File

@@ -119,8 +119,6 @@ public class ForumListFragment extends BaseEventFragment implements
@Override
public void onStart() {
super.onStart();
notificationManager.blockAllForumPostNotifications();
notificationManager.clearAllForumPostNotifications();
loadForums();
loadAvailableForums();
list.startPeriodicUpdate();
@@ -129,7 +127,6 @@ public class ForumListFragment extends BaseEventFragment implements
@Override
public void onStop() {
super.onStop();
notificationManager.unblockAllForumPostNotifications();
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate();

View File

@@ -1,66 +0,0 @@
package org.briarproject.briar.android.fragment;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import java.util.ArrayList;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SFDialogFragment extends DialogFragment {
public static SFDialogFragment newInstance(ArrayList<String> apps) {
SFDialogFragment frag = new SFDialogFragment();
Bundle args = new Bundle();
args.putStringArrayList("apps", apps);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder =
new AlertDialog.Builder(
getActivity(),
R.style.BriarDialogThemeNoFilter);
builder.setTitle(R.string.screen_filter_title);
LayoutInflater li = getActivity().getLayoutInflater();
//Pass null here because it's an AlertDialog
View v =
li.inflate(R.layout.alert_dialog_checkbox, null,
false);
TextView t = (TextView) v.findViewById(R.id.alert_dialog_text);
final ArrayList<String> apps =
getArguments().getStringArrayList("apps");
t.setText(getString(R.string.screen_filter_body, TextUtils
.join("\n", apps)));
final CheckBox cb =
(CheckBox) v.findViewById(
R.id.checkBox_screen_filter_reminder);
builder.setNeutralButton(R.string.continue_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
((BaseActivity) getActivity())
.rememberShownApps(apps, cb.isChecked());
}
});
builder.setView(v);
return builder.create();
}
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.briar.android.fragment;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import java.util.ArrayList;
import javax.annotation.Nullable;
@NotNullByDefault
public class ScreenFilterDialogFragment extends DialogFragment {
public static ScreenFilterDialogFragment newInstance(
ArrayList<String> apps) {
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
Bundle args = new Bundle();
args.putStringArrayList("apps", apps);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
R.style.BriarDialogThemeNoFilter);
builder.setTitle(R.string.screen_filter_title);
ArrayList<String> apps = getArguments().getStringArrayList("apps");
builder.setMessage(getString(R.string.screen_filter_body,
TextUtils.join("\n", apps)));
builder.setNeutralButton(R.string.continue_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
return builder.create();
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -9,6 +8,8 @@ import android.view.ViewGroup;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import javax.annotation.Nullable;
public class SignOutFragment extends BaseFragment {
private static final String TAG = SignOutFragment.class.getName();

View File

@@ -33,7 +33,6 @@ import im.delight.android.identicons.IdenticonDrawable;
import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
@@ -94,7 +93,6 @@ public class IntroductionMessageFragment extends BaseFragment
View v = inflater.inflate(R.layout.introduction_message, container,
false);
ui = new ViewHolder(v);
ui.text.setVisibility(GONE);
ui.message.setSendButtonEnabled(false);
return v;
@@ -156,17 +154,11 @@ public class IntroductionMessageFragment extends BaseFragment
ui.contactName1.setText(c1.getAuthor().getName());
ui.contactName2.setText(c2.getAuthor().getName());
// set introduction text
ui.text.setText(String.format(
getString(R.string.introduction_message_text),
c1.getAuthor().getName(), c2.getAuthor().getName()));
// set button action
ui.message.setListener(IntroductionMessageFragment.this);
// hide progress bar and show views
ui.progressBar.setVisibility(GONE);
ui.text.setVisibility(VISIBLE);
ui.message.setSendButtonEnabled(true);
ui.message.showSoftKeyboard();
}
@@ -234,7 +226,6 @@ public class IntroductionMessageFragment extends BaseFragment
private final ProgressBar progressBar;
private final CircleImageView avatar1, avatar2;
private final TextView contactName1, contactName2;
private final TextView text;
private final TextInputView message;
private ViewHolder(View v) {
@@ -243,7 +234,6 @@ public class IntroductionMessageFragment extends BaseFragment
avatar2 = (CircleImageView) v.findViewById(R.id.avatarContact2);
contactName1 = (TextView) v.findViewById(R.id.nameContact1);
contactName2 = (TextView) v.findViewById(R.id.nameContact2);
text = (TextView) v.findViewById(R.id.introductionText);
message = (TextInputView) v
.findViewById(R.id.introductionMessageView);
}

View File

@@ -29,6 +29,7 @@ import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.FeedFragment;
import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.forum.ForumListFragment;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
@@ -45,6 +46,9 @@ import javax.inject.Inject;
import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
import static android.support.v4.view.GravityCompat.START;
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
public class NavDrawerActivity extends BriarActivity implements
BaseFragmentListener, TransportStateListener,
@@ -128,6 +132,12 @@ public class NavDrawerActivity extends BriarActivity implements
public void onStart() {
super.onStart();
updateTransports();
controller.showExpiryWarning(new UiResultHandler<Boolean>(this) {
@Override
public void onResultUi(Boolean showWarning) {
if (showWarning) showExpiryWarning();
}
});
}
private void exitIfStartupFailed(Intent intent) {
@@ -254,6 +264,34 @@ public class NavDrawerActivity extends BriarActivity implements
// Do nothing for now
}
@SuppressWarnings("ConstantConditions")
private void showExpiryWarning() {
int daysUntilExpiry = getDaysUntilExpiry();
if (daysUntilExpiry < 0) signOut();
// show expiry warning text
final ViewGroup
expiryWarning = (ViewGroup) findViewById(R.id.expiryWarning);
TextView expiryWarningText =
(TextView) expiryWarning.findViewById(R.id.expiryWarningText);
expiryWarningText.setText(getResources()
.getQuantityString(R.plurals.expiry_warning, daysUntilExpiry,
daysUntilExpiry));
// make close button functional
ImageView expiryWarningClose =
(ImageView) expiryWarning.findViewById(R.id.expiryWarningClose);
expiryWarningClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
controller.expiryWarningDismissed();
expiryWarning.setVisibility(GONE);
}
});
expiryWarning.setVisibility(VISIBLE);
}
private void initializeTransports(final LayoutInflater inflater) {
transports = new ArrayList<>(3);

View File

@@ -3,10 +3,15 @@ package org.briarproject.briar.android.navdrawer;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.controller.handler.ResultHandler;
@NotNullByDefault
public interface NavDrawerController extends ActivityLifecycleController {
boolean isTransportRunning(TransportId transportId);
void showExpiryWarning(final ResultHandler<Boolean> handler);
void expiryWarningDismissed();
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.navdrawer;
import android.app.Activity;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
@@ -14,7 +15,10 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -22,6 +26,9 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -30,18 +37,21 @@ public class NavDrawerControllerImpl extends DbControllerImpl
private static final Logger LOG =
Logger.getLogger(NavDrawerControllerImpl.class.getName());
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
private final PluginManager pluginManager;
private final SettingsManager settingsManager;
private final EventBus eventBus;
private volatile TransportStateListener listener;
@Inject
NavDrawerControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
PluginManager pluginManager, EventBus eventBus) {
LifecycleManager lifecycleManager, PluginManager pluginManager,
SettingsManager settingsManager, EventBus eventBus) {
super(dbExecutor, lifecycleManager);
this.pluginManager = pluginManager;
this.settingsManager = settingsManager;
this.eventBus = eventBus;
}
@@ -92,6 +102,63 @@ public class NavDrawerControllerImpl extends DbControllerImpl
});
}
@Override
public void showExpiryWarning(final ResultHandler<Boolean> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
if (warningInt == 0) {
// we have not warned before
handler.onResult(true);
} else {
long warningLong = warningInt * 1000L;
long now = System.currentTimeMillis();
long daysSinceLastWarning =
(now - warningLong) / 1000 / 60 / 60 / 24;
long daysBeforeExpiry =
(EXPIRY_DATE - now) / 1000 / 60 / 60 / 24;
if (daysSinceLastWarning >= 30) {
handler.onResult(true);
return;
}
if (daysBeforeExpiry <= 3 && daysSinceLastWarning > 0) {
handler.onResult(true);
return;
}
handler.onResult(false);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
@Override
public void expiryWarningDismissed() {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Settings settings = new Settings();
int date = (int) (System.currentTimeMillis() / 1000L);
settings.putInt(EXPIRY_DATE_WARNING, date);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
@Override
public boolean isTransportRunning(TransportId transportId) {
Plugin plugin = pluginManager.getPlugin(transportId);

View File

@@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener;
import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.privategroup.GroupMember;
import org.briarproject.briar.api.privategroup.GroupMessage;
@@ -60,9 +61,10 @@ class GroupControllerImpl extends
@CryptoExecutor Executor cryptoExecutor,
PrivateGroupManager privateGroupManager,
GroupMessageFactory groupMessageFactory, EventBus eventBus,
Clock clock, AndroidNotificationManager notificationManager) {
MessageTracker messageTracker, Clock clock,
AndroidNotificationManager notificationManager) {
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
eventBus, clock, notificationManager);
eventBus, clock, notificationManager, messageTracker);
this.privateGroupManager = privateGroupManager;
this.groupMessageFactory = groupMessageFactory;
}
@@ -106,7 +108,7 @@ class GroupControllerImpl extends
(GroupInvitationResponseReceivedEvent) e;
final GroupInvitationResponse r =
(GroupInvitationResponse) g.getResponse();
if (getGroupId().equals(r.getGroupId()) && r.wasAccepted()) {
if (getGroupId().equals(r.getShareableId()) && r.wasAccepted()) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.privategroup.conversation;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import org.briarproject.bramble.api.identity.Author;
@@ -12,6 +11,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.threaded.ThreadItem;
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@UiThread

View File

@@ -1,25 +1,15 @@
package org.briarproject.briar.android.privategroup.conversation;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.briar.android.threaded.BaseThreadItemViewHolder;
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityIcon;
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityString;
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
@UiThread
@NotNullByDefault
@@ -27,16 +17,10 @@ class JoinMessageItemViewHolder
extends BaseThreadItemViewHolder<GroupMessageItem> {
private final boolean isCreator;
private final ImageView icon;
private final TextView info;
private final Button options;
JoinMessageItemViewHolder(View v, boolean isCreator) {
super(v);
this.isCreator = isCreator;
icon = (ImageView) v.findViewById(R.id.icon);
info = (TextView) v.findViewById(R.id.info);
options = (Button) v.findViewById(R.id.optionsButton);
}
@Override
@@ -56,9 +40,6 @@ class JoinMessageItemViewHolder
getContext().getString(R.string.groups_member_joined,
item.getAuthor().getName()));
}
icon.setVisibility(View.GONE);
info.setVisibility(View.GONE);
options.setVisibility(View.GONE);
}
private void bind(final JoinMessageItem item) {
@@ -75,32 +56,6 @@ class JoinMessageItemViewHolder
item.getAuthor().getName()));
}
}
if (item.getStatus() == OURSELVES || item.getStatus() == UNKNOWN) {
icon.setVisibility(View.GONE);
info.setVisibility(View.GONE);
options.setVisibility(View.GONE);
} else {
icon.setVisibility(View.VISIBLE);
icon.setImageResource(getVisibilityIcon(item.getVisibility()));
info.setVisibility(View.VISIBLE);
info.setText(getVisibilityString(getContext(), item.getVisibility(),
item.getAuthor().getName()));
if (item.getVisibility() == INVISIBLE) {
options.setVisibility(View.VISIBLE);
options.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i =
new Intent(ctx, RevealContactsActivity.class);
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
ctx.startActivity(i);
}
});
} else {
options.setVisibility(View.GONE);
}
}
}
}

View File

@@ -1,58 +0,0 @@
package org.briarproject.briar.android.privategroup.creation;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.contactselection.ContactSelectorActivity;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
import java.util.Collection;
import javax.inject.Inject;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class BaseGroupInviteActivity
extends ContactSelectorActivity implements MessageFragmentListener {
@Inject
CreateGroupController controller;
@Override
public void contactsSelected(Collection<ContactId> contacts) {
super.contactsSelected(contacts);
showNextFragment(new CreateGroupMessageFragment());
}
@Override
public boolean onButtonClick(String message) {
if (groupId == null)
throw new IllegalStateException("GroupId was not initialized");
controller.sendInvitation(groupId, contacts, message,
new UiResultExceptionHandler<Void, DbException>(this) {
@Override
public void onResultUi(Void result) {
setResult(RESULT_OK);
supportFinishAfterTransition();
}
@Override
public void onExceptionUi(DbException exception) {
setResult(RESULT_CANCELED);
handleDbException(exception);
}
});
return true;
}
@Override
public int getMaximumMessageLength() {
return MAX_GROUP_INVITATION_MSG_LENGTH;
}
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.creation;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -10,14 +9,20 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
import javax.annotation.Nullable;
import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class CreateGroupActivity extends BaseGroupInviteActivity implements
CreateGroupListener, MessageFragmentListener {
public class CreateGroupActivity extends BriarActivity
implements CreateGroupListener {
@Inject
CreateGroupController controller;
@Override
public void injectActivity(ActivityComponent component) {
@@ -28,32 +33,20 @@ public class CreateGroupActivity extends BaseGroupInviteActivity implements
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_fragment_container);
if (bundle == null) {
showInitialFragment(new CreateGroupFragment());
}
}
@Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
// At this point, the group had been created already,
// so don't allow to create it again.
openNewGroup();
overridePendingTransition(R.anim.screen_old_in,
R.anim.screen_new_out);
} else {
super.onBackPressed();
}
}
@Override
public void onGroupNameChosen(String name) {
controller.createGroup(name,
new UiResultExceptionHandler<GroupId, DbException>(this) {
@Override
public void onResultUi(GroupId g) {
groupId = g;
switchToContactSelectorFragment(g);
openNewGroup(g);
}
@Override
@@ -63,16 +56,10 @@ public class CreateGroupActivity extends BaseGroupInviteActivity implements
});
}
private void switchToContactSelectorFragment(GroupId g) {
showNextFragment(GroupInviteFragment.newInstance(g));
}
private void openNewGroup() {
private void openNewGroup(GroupId g) {
Intent i = new Intent(this, GroupActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.putExtra(GROUP_ID, g.getBytes());
startActivity(i);
// finish this activity, so we can't come back to it
finish();
}
}

View File

@@ -2,18 +2,27 @@ package org.briarproject.briar.android.privategroup.creation;
import android.content.Context;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
public class CreateGroupFragment extends BaseFragment {
@@ -21,8 +30,10 @@ public class CreateGroupFragment extends BaseFragment {
public final static String TAG = CreateGroupFragment.class.getName();
private CreateGroupListener listener;
private EditText name;
private Button button;
private EditText nameEntry;
private Button createGroupButton;
private TextInputLayout nameLayout;
private ProgressBar progress;
@Override
public void onAttach(Context context) {
@@ -34,42 +45,54 @@ public class CreateGroupFragment extends BaseFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// inflate view
View v = inflater.inflate(R.layout.fragment_create_group, container,
false);
name = (EditText) v.findViewById(R.id.name);
name.addTextChangedListener(new TextWatcher() {
nameEntry = (EditText) v.findViewById(R.id.name);
nameEntry.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
validateName();
public void onTextChanged(CharSequence s, int start,
int lengthBefore, int lengthAfter) {
enableOrDisableCreateButton();
}
@Override
public void afterTextChanged(Editable s) {
}
});
button = (Button) v.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
nameEntry.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public void onClick(View v) {
listener.hideSoftKeyboard(name);
listener.onGroupNameChosen(name.getText().toString());
public boolean onEditorAction(TextView v, int actionId,
KeyEvent e) {
createGroup();
return true;
}
});
nameLayout = (TextInputLayout) v.findViewById(R.id.nameLayout);
createGroupButton = (Button) v.findViewById(R.id.button);
createGroupButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
createGroup();
}
});
progress = (ProgressBar) v.findViewById(R.id.progressBar);
return v;
}
@Override
public void onStart() {
super.onStart();
listener.showSoftKeyboard(name);
listener.showSoftKeyboard(nameEntry);
}
@Override
@@ -82,12 +105,27 @@ public class CreateGroupFragment extends BaseFragment {
return TAG;
}
private void validateName() {
String name = this.name.getText().toString();
if (name.length() < 1 || name.length() > MAX_GROUP_NAME_LENGTH)
button.setEnabled(false);
else if(!button.isEnabled())
button.setEnabled(true);
private void enableOrDisableCreateButton() {
if (createGroupButton == null) return; // Not created yet
createGroupButton.setEnabled(validateName());
}
private boolean validateName() {
String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length;
if (length > MAX_GROUP_NAME_LENGTH) {
nameLayout.setError(getString(R.string.name_too_long));
return false;
}
nameLayout.setError(null);
return length > 0;
}
private void createGroup() {
if (!validateName()) return;
listener.hideSoftKeyboard(nameEntry);
createGroupButton.setVisibility(GONE);
progress.setVisibility(VISIBLE);
listener.onGroupNameChosen(nameEntry.getText().toString());
}
}

View File

@@ -6,7 +6,7 @@ import dagger.Module;
import dagger.Provides;
@Module
public class GroupCreateModule {
public class CreateGroupModule {
@ActivityScope
@Provides

View File

@@ -3,25 +3,43 @@ package org.briarproject.briar.android.privategroup.creation;
import android.content.Intent;
import android.os.Bundle;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contactselection.ContactSelectorActivity;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
public class GroupInviteActivity extends BaseGroupInviteActivity
import java.util.Collection;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class GroupInviteActivity extends ContactSelectorActivity
implements MessageFragmentListener {
@Inject
CreateGroupController controller;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(Bundle bundle) {
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
Intent i = getIntent();
byte[] g = i.getByteArrayExtra(GROUP_ID);
if (g == null) throw new IllegalStateException("No GroupId in intent.");
if (g == null) throw new IllegalStateException("No GroupId in intent");
groupId = new GroupId(g);
if (bundle == null) {
@@ -29,4 +47,36 @@ public class GroupInviteActivity extends BaseGroupInviteActivity
}
}
@Override
public void contactsSelected(Collection<ContactId> contacts) {
super.contactsSelected(contacts);
showNextFragment(new CreateGroupMessageFragment());
}
@Override
public boolean onButtonClick(String message) {
if (groupId == null)
throw new IllegalStateException("GroupId was not initialized");
controller.sendInvitation(groupId, contacts, message,
new UiResultExceptionHandler<Void, DbException>(this) {
@Override
public void onResultUi(Void result) {
setResult(RESULT_OK);
supportFinishAfterTransition();
}
@Override
public void onExceptionUi(DbException exception) {
setResult(RESULT_CANCELED);
handleDbException(exception);
}
});
return true;
}
@Override
public int getMaximumMessageLength() {
return MAX_GROUP_INVITATION_MSG_LENGTH;
}
}

View File

@@ -79,15 +79,12 @@ class GroupListControllerImpl extends DbControllerImpl
throw new IllegalStateException(
"GroupListListener needs to be attached");
eventBus.addListener(this);
notificationManager.blockAllGroupMessageNotifications();
notificationManager.clearAllGroupMessageNotifications();
}
@Override
@CallSuper
public void onStop() {
eventBus.removeListener(this);
notificationManager.unblockAllGroupMessageNotifications();
}
@Override

View File

@@ -36,7 +36,6 @@ class MemberListAdapter extends
@Override
public boolean areContentsTheSame(MemberListItem m1, MemberListItem m2) {
if (m1.isOnline() != m2.isOnline()) return false;
if (m1.getVisibility() != m2.getVisibility()) return false;
if (m1.getContactId() != m2.getContactId()) return false;
if (m1.getStatus() != m2.getStatus()) return false;
return true;

View File

@@ -1,14 +1,12 @@
package org.briarproject.briar.android.privategroup.memberlist;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Author.Status;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.privategroup.GroupMember;
import org.briarproject.briar.api.privategroup.Visibility;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@@ -40,10 +38,6 @@ class MemberListItem {
return groupMember.getContactId();
}
Visibility getVisibility() {
return groupMember.getVisibility();
}
boolean isOnline() {
return online;
}

View File

@@ -11,9 +11,6 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.view.AuthorView;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityIcon;
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityString;
@UiThread
@NotNullByDefault
@@ -22,16 +19,12 @@ class MemberListItemHolder extends RecyclerView.ViewHolder {
private final AuthorView author;
private final ImageView bulb;
private final TextView creator;
private final ImageView icon;
private final TextView info;
MemberListItemHolder(View v) {
super(v);
author = (AuthorView) v.findViewById(R.id.authorView);
bulb = (ImageView) v.findViewById(R.id.bulbView);
creator = (TextView) v.findViewById(R.id.creatorView);
icon = (ImageView) v.findViewById(R.id.icon);
info = (TextView) v.findViewById(R.id.info);
}
protected void bind(MemberListItem item) {
@@ -64,19 +57,6 @@ class MemberListItemHolder extends RecyclerView.ViewHolder {
} else {
creator.setVisibility(View.GONE);
}
// visibility information
if (item.getStatus() == OURSELVES || item.getStatus() == UNKNOWN) {
icon.setVisibility(View.GONE);
info.setVisibility(View.GONE);
} else {
icon.setVisibility(View.VISIBLE);
icon.setImageResource(getVisibilityIcon(item.getVisibility()));
info.setVisibility(View.VISIBLE);
info.setText(
getVisibilityString(info.getContext(), item.getVisibility(),
item.getMember().getName()));
}
}
}

View File

@@ -146,7 +146,7 @@ public class BriarReportPrimer implements ReportPrimer {
NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
boolean wifiAvailable = wifi != null && wifi.isAvailable();
// Is wifi enabled?
o = ctx.getSystemService(WIFI_SERVICE);
o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
WifiManager wm = (WifiManager) o;
boolean wifiEnabled = wm != null &&
wm.getWifiState() == WIFI_STATE_ENABLED;

View File

@@ -6,6 +6,7 @@ import android.content.Intent;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.ListPreference;
@@ -54,6 +55,7 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGT
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_LOCK_SCREEN;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_PRIVATE;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_NAME;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_URI;
@@ -81,6 +83,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
private CheckBoxPreference notifyForumPosts;
private CheckBoxPreference notifyBlogPosts;
private CheckBoxPreference notifyVibration;
private CheckBoxPreference notifyLockscreen;
private Preference notifySound;
// Fields that are accessed from background threads must be volatile
@@ -114,6 +118,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
"pref_key_notify_blog_posts");
notifyVibration = (CheckBoxPreference) findPreference(
"pref_key_notify_vibration");
notifyLockscreen = (CheckBoxPreference) findPreference(
"pref_key_notify_lock_screen");
notifySound = findPreference("pref_key_notify_sound");
enableBluetooth.setOnPreferenceChangeListener(this);
@@ -123,7 +129,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
notifyForumPosts.setOnPreferenceChangeListener(this);
notifyBlogPosts.setOnPreferenceChangeListener(this);
notifyVibration.setOnPreferenceChangeListener(this);
if (Build.VERSION.SDK_INT >= 21) {
notifyLockscreen.setVisible(true);
notifyLockscreen.setOnPreferenceChangeListener(this);
}
notifySound.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
@@ -234,6 +243,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
notifyVibration.setChecked(settings.getBoolean(
PREF_NOTIFY_VIBRATION, true));
notifyLockscreen.setChecked(settings.getBoolean(
PREF_NOTIFY_LOCK_SCREEN, false));
String text;
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
String ringtoneName =
@@ -290,6 +302,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
Settings s = new Settings();
s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) o);
storeSettings(s);
} else if (preference == notifyLockscreen) {
Settings s = new Settings();
s.putBoolean(PREF_NOTIFY_LOCK_SCREEN, (Boolean) o);
storeSettings(s);
}
return true;
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.sharing;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem;
@@ -25,6 +24,7 @@ import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;

View File

@@ -4,9 +4,6 @@ import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.os.StrictMode.VmPolicy;
import android.support.v7.preference.PreferenceManager;
import android.transition.Fade;
@@ -22,27 +19,18 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
import static org.briarproject.briar.android.TestingConstants.TESTING;
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
public class SplashScreenActivity extends BaseActivity {
private static final Logger LOG =
Logger.getLogger(SplashScreenActivity.class.getName());
// This build expires on 1 May 2017
private static final long EXPIRY_DATE = 1493593200 * 1000L;
@Inject
protected ConfigController configController;
@Inject
protected AndroidExecutor androidExecutor;
public SplashScreenActivity() {
Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
enableStrictMode();
}
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
@@ -83,23 +71,6 @@ public class SplashScreenActivity extends BaseActivity {
}
}
@Override
protected void showNewScreenFilterWarning() {
}
private void enableStrictMode() {
if (TESTING) {
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
threadPolicy.detectAll();
threadPolicy.penaltyLog();
StrictMode.setThreadPolicy(threadPolicy.build());
VmPolicy.Builder vmPolicy = new VmPolicy.Builder();
vmPolicy.detectAll();
vmPolicy.penaltyLog();
StrictMode.setVmPolicy(vmPolicy.build());
}
}
private void setPreferencesDefaults() {
androidExecutor.runOnBackgroundThread(new Runnable() {
@Override

View File

@@ -1,6 +1,6 @@
package org.briarproject.briar.android.threaded;
import android.support.annotation.Nullable;
import android.os.Handler;
import android.support.annotation.UiThread;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -14,6 +14,8 @@ import org.briarproject.briar.android.util.VersionedAdapter;
import java.util.Collection;
import javax.annotation.Nullable;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
@UiThread
@@ -26,6 +28,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
protected final NestedTreeList<I> items = new NestedTreeList<>();
private final ThreadItemListener<I> listener;
private final LinearLayoutManager layoutManager;
private final Handler handler = new Handler();
private volatile int revision = 0;
@@ -64,6 +67,17 @@ public class ThreadItemAdapter<I extends ThreadItem>
revision++;
}
void setItemWithIdVisible(MessageId messageId) {
int pos = 0;
for (I item : items) {
if (item.getId().equals(messageId)) {
layoutManager.scrollToPosition(pos);
break;
}
pos++;
}
}
public void setItems(Collection<I> items) {
this.items.clear();
this.items.addAll(items);
@@ -144,7 +158,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
/**
* Returns the position of the first unread item below the current viewport
*/
public int getVisibleUnreadPosBottom() {
int getVisibleUnreadPosBottom() {
final int positionBottom = layoutManager.findLastVisibleItemPosition();
if (positionBottom == NO_POSITION) return NO_POSITION;
for (int i = positionBottom + 1; i < items.size(); i++) {
@@ -156,7 +170,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
/**
* Returns the position of the first unread item above the current viewport
*/
public int getVisibleUnreadPosTop() {
int getVisibleUnreadPosTop() {
final int positionTop = layoutManager.findFirstVisibleItemPosition();
int position = NO_POSITION;
for (int i = 0; i < items.size(); i++) {

View File

@@ -0,0 +1,15 @@
package org.briarproject.briar.android.threaded;
import org.briarproject.bramble.api.sync.MessageId;
import java.util.List;
import javax.annotation.Nullable;
public interface ThreadItemList<I extends ThreadItem> extends List<I> {
@Nullable
MessageId getFirstVisibleItemId();
void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId);
}

View File

@@ -0,0 +1,22 @@
package org.briarproject.briar.android.threaded;
import org.briarproject.bramble.api.sync.MessageId;
import java.util.ArrayList;
import javax.annotation.Nullable;
public class ThreadItemListImpl<I extends ThreadItem> extends ArrayList<I>
implements ThreadItemList<I> {
private MessageId bottomVisibleItemId;
@Override
public MessageId getFirstVisibleItemId() {
return bottomVisibleItemId;
}
public void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId) {
this.bottomVisibleItemId = bottomVisibleItemId;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.threaded;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
@@ -26,6 +25,7 @@ import org.briarproject.briar.android.controller.SharingController;
import org.briarproject.briar.android.controller.SharingController.SharingListener;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDataSource;
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.view.TextInputView;
@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.support.design.widget.Snackbar.make;
@@ -51,7 +52,7 @@ import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCo
public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader>
extends BriarActivity
implements ThreadListListener<H>, TextInputListener, SharingListener,
ThreadItemListener<I> {
ThreadItemListener<I>, ThreadListDataSource {
protected static final String KEY_REPLY_ID = "replyId";
@@ -68,6 +69,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
private MessageId replyId;
protected abstract ThreadListController<G, I, H> getController();
@Inject
protected SharingController sharingController;
@@ -104,6 +106,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
updateUnreadCount();
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView,
int newState) {
@@ -139,11 +142,22 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
if (replyIdBytes != null) replyId = new MessageId(replyIdBytes);
}
loadItems();
sharingController.setSharingListener(this);
loadSharingContacts();
}
@Override
@Nullable
public MessageId getFirstVisibleMessageId() {
if (layoutManager != null && adapter != null) {
int position =
layoutManager.findFirstVisibleItemPosition();
I i = adapter.getItemAt(position);
return i == null ? null : i.getId();
}
return null;
}
protected abstract A createAdapter(LinearLayoutManager layoutManager);
protected void loadNamedGroup() {
@@ -167,16 +181,16 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
protected void loadItems() {
final int revision = adapter.getRevision();
getController().loadItems(
new UiResultExceptionHandler<Collection<I>, DbException>(this) {
new UiResultExceptionHandler<ThreadItemList<I>, DbException>(
this) {
@Override
public void onResultUi(Collection<I> items) {
public void onResultUi(ThreadItemList<I> items) {
if (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (items.isEmpty()) {
list.showData();
} else {
adapter.setItems(items);
list.showData();
initList(items);
updateTextInput(replyId);
}
} else {
@@ -192,6 +206,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
});
}
private void initList(final ThreadItemList<I> items) {
adapter.setItems(items);
MessageId messageId = items.getFirstVisibleItemId();
if (messageId != null)
adapter.setItemWithIdVisible(messageId);
updateUnreadCount();
list.showData();
}
protected void loadSharingContacts() {
getController().loadSharingContacts(
new UiResultExceptionHandler<Collection<ContactId>, DbException>(
@@ -215,6 +238,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
public void onStart() {
super.onStart();
sharingController.onStart();
loadItems();
list.startPeriodicUpdate();
}

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
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.android.DestroyableContext;
import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
@@ -30,7 +31,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
void loadItem(H header, ResultExceptionHandler<I, DbException> handler);
void loadItems(ResultExceptionHandler<Collection<I>, DbException> handler);
void loadItems(ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
void markItemRead(I item);
@@ -41,7 +42,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
void deleteNamedGroup(ExceptionHandler<DbException> handler);
interface ThreadListListener<H> extends DestroyableContext {
interface ThreadListListener<H> extends ThreadListDataSource {
@UiThread
void onHeaderReceived(H header);
@@ -52,4 +53,10 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
void onInvitationAccepted(ContactId c);
}
interface ThreadListDataSource extends DestroyableContext {
@UiThread @Nullable
MessageId getFirstVisibleMessageId();
}
}

View File

@@ -22,14 +22,13 @@ import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.NamedGroup;
import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.api.client.ThreadedMessage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -55,18 +54,21 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
protected final AndroidNotificationManager notificationManager;
protected final Executor cryptoExecutor;
protected final Clock clock;
private final MessageTracker messageTracker;
protected volatile L listener;
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, IdentityManager identityManager,
@CryptoExecutor Executor cryptoExecutor, EventBus eventBus,
Clock clock, AndroidNotificationManager notificationManager) {
Clock clock, AndroidNotificationManager notificationManager,
MessageTracker messageTracker) {
super(dbExecutor, lifecycleManager);
this.identityManager = identityManager;
this.cryptoExecutor = cryptoExecutor;
this.notificationManager = notificationManager;
this.clock = clock;
this.eventBus = eventBus;
this.messageTracker = messageTracker;
}
@Override
@@ -97,6 +99,21 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@Override
public void onActivityDestroy() {
final MessageId messageId = listener.getFirstVisibleMessageId();
if (messageId != null) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
messageTracker
.storeMessageId(groupId, messageId);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
}
@CallSuper
@@ -144,7 +161,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@Override
public void loadItems(
final ResultExceptionHandler<Collection<I>, DbException> handler) {
final ResultExceptionHandler<ThreadItemList<I>, DbException> handler) {
checkGroupId();
runOnDbThread(new Runnable() {
@Override
@@ -293,11 +310,16 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@DatabaseExecutor
protected abstract void deleteNamedGroup(G groupItem) throws DbException;
private List<I> buildItems(Collection<H> headers) {
List<I> items = new ArrayList<>();
private ThreadItemList<I> buildItems(Collection<H> headers)
throws DbException {
ThreadItemList<I> items = new ThreadItemListImpl<>();
for (H h : headers) {
items.add(buildItem(h, bodyCache.get(h.getId())));
}
MessageId msgId = messageTracker.loadStoredMessageId(groupId);
if (LOG.isLoggable(INFO))
LOG.info("Loaded last top visible message id " + msgId);
items.setFirstVisibleId(msgId);
return items;
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.util;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
@@ -23,6 +22,8 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.view.ArticleMovementMethod;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import javax.annotation.Nullable;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE;
@@ -30,6 +31,7 @@ import static android.text.format.DateUtils.FORMAT_ABBREV_TIME;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
public class UiUtils {
@@ -63,6 +65,12 @@ public class UiUtils {
MIN_DATE_RESOLUTION, flags).toString();
}
public static int getDaysUntilExpiry() {
long now = System.currentTimeMillis();
long daysBeforeExpiry = (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24;
return (int) daysBeforeExpiry;
}
public static SpannableStringBuilder getTeaser(Context ctx, Spanned body) {
if (body.length() < TEASER_LENGTH)
throw new IllegalArgumentException(

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.view;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.support.annotation.DimenRes;
@@ -16,9 +15,7 @@ import android.widget.TextView;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Author.Status;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.util.UiUtils;
import javax.annotation.Nullable;
@@ -27,12 +24,10 @@ import de.hdodenhof.circleimageview.CircleImageView;
import im.delight.android.identicons.IdenticonDrawable;
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.graphics.Typeface.BOLD;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static org.briarproject.bramble.api.identity.Author.Status.NONE;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@UiThread
public class AuthorView extends RelativeLayout {
@@ -110,24 +105,16 @@ public class AuthorView extends RelativeLayout {
requestLayout();
}
public void setBlogLink(final GroupId groupId) {
public void setAuthorClickable(OnClickListener listener) {
setClickable(true);
TypedValue outValue = new TypedValue();
getContext().getTheme().resolveAttribute(
android.R.attr.selectableItemBackground, outValue, true);
setBackgroundResource(outValue.resourceId);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(getContext(), BlogActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(i);
}
});
setOnClickListener(listener);
}
public void unsetBlogLink() {
public void setAuthorNotClickable() {
setClickable(false);
setBackgroundResource(android.R.color.transparent);
setOnClickListener(null);

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
@@ -13,6 +12,8 @@ import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import javax.annotation.Nullable;
@UiThread
@NotNullByDefault
public class UnreadMessageButton extends FrameLayout {
@@ -36,8 +37,7 @@ public class UnreadMessageButton extends FrameLayout {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater
.inflate(R.layout.unread_message_button, this, true);
inflater.inflate(R.layout.unread_message_button, this, true);
fab = (FloatingActionButton) findViewById(R.id.fab);
unread = (TextView) findViewById(R.id.unreadCountView);
@@ -64,15 +64,11 @@ public class UnreadMessageButton extends FrameLayout {
public void setUnreadCount(int count) {
if (count == 0) {
fab.setVisibility(GONE);
// fab.hide();
unread.setVisibility(GONE);
setVisibility(INVISIBLE);
} else {
// FIXME: Use animations when upgrading to support library 24.2.0
// https://code.google.com/p/android/issues/detail?id=216469
fab.setVisibility(VISIBLE);
// if (!fab.isShown()) fab.show();
unread.setVisibility(VISIBLE);
setVisibility(VISIBLE);
unread.setText(String.valueOf(count));
}
}

View File

@@ -0,0 +1,51 @@
package org.briarproject.briar.android.widget;
import android.content.Context;
import android.support.annotation.AttrRes;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import static android.view.MotionEvent.FLAG_WINDOW_IS_OBSCURED;
@NotNullByDefault
public class TapSafeFrameLayout extends FrameLayout {
@Nullable
private OnTapFilteredListener listener;
public TapSafeFrameLayout(Context context) {
super(context);
setFilterTouchesWhenObscured(false);
}
public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setFilterTouchesWhenObscured(false);
}
public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
setFilterTouchesWhenObscured(false);
}
public void setOnTapFilteredListener(OnTapFilteredListener listener) {
this.listener = listener;
}
@Override
public boolean onFilterTouchEventForSecurity(MotionEvent e) {
boolean filter = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0;
if (filter && listener != null) listener.onTapFiltered();
return !filter;
}
public interface OnTapFilteredListener {
void onTapFiltered();
}
}

View File

@@ -18,19 +18,14 @@ public interface AndroidNotificationManager {
String PREF_NOTIFY_RINGTONE_NAME = "notifyRingtoneName";
String PREF_NOTIFY_RINGTONE_URI = "notifyRingtoneUri";
String PREF_NOTIFY_VIBRATION = "notifyVibration";
String PREF_NOTIFY_LOCK_SCREEN = "notifyLockScreen";
void clearContactNotification(ContactId c);
void clearAllContactNotifications();
void clearGroupMessageNotification(GroupId g);
void clearAllGroupMessageNotifications();
void clearForumPostNotification(GroupId g);
void clearAllForumPostNotifications();
void clearBlogPostNotification(GroupId g);
void clearAllBlogPostNotifications();
@@ -43,18 +38,6 @@ public interface AndroidNotificationManager {
void unblockNotification(GroupId g);
void blockAllContactNotifications();
void unblockAllContactNotifications();
void blockAllGroupMessageNotifications();
void unblockAllGroupMessageNotifications();
void blockAllForumPostNotifications();
void unblockAllForumPostNotifications();
void blockAllBlogPostNotifications();
void unblockAllBlogPostNotifications();

View File

@@ -2,14 +2,10 @@ package org.briarproject.briar.api.android;
import android.support.annotation.UiThread;
import java.util.Collection;
import java.util.Set;
public interface ScreenFilterMonitor {
@UiThread
Set<String> getApps();
@UiThread
void storeAppsAsShown(Collection<String> s, boolean persistent);
}

View File

@@ -104,8 +104,9 @@ public class EmojiPageView extends FrameLayout {
emojiSize + 2 * pad));
view = emojiView;
}
String emoji = model.getEmoji()[position];
view.setEmoji(emoji);
view.setEmoji(model.getEmoji()[position]);
return view;
}
}

View File

@@ -1,29 +1,21 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.text.TextUtils;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.view.ViewConfiguration;
import android.widget.TextView;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
import javax.annotation.Nullable;
import static android.text.TextUtils.TruncateAt.END;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.widget.TextView.BufferType.SPANNABLE;
@UiThread
public class EmojiTextView extends TextView {
private CharSequence source;
private boolean needsEllipsizing;
public class EmojiTextView extends AppCompatTextView {
public EmojiTextView(Context context) {
this(context, null);
@@ -42,13 +34,9 @@ public class EmojiTextView extends TextView {
@Override
public void setText(@Nullable CharSequence text, BufferType type) {
source = EmojiProvider.getInstance(getContext()).emojify(text, this);
setTextEllipsized(source);
}
private void setTextEllipsized(final @Nullable CharSequence source) {
super.setText(needsEllipsizing ? ellipsize(source) : source, SPANNABLE);
CharSequence source =
EmojiProvider.getInstance(getContext()).emojify(text, this);
super.setText(source, SPANNABLE);
}
@Override
@@ -57,26 +45,6 @@ public class EmojiTextView extends TextView {
else super.invalidateDrawable(drawable);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int size = MeasureSpec.getSize(widthMeasureSpec);
final int mode = MeasureSpec.getMode(widthMeasureSpec);
if (getEllipsize() == END &&
!TextUtils.isEmpty(source) &&
(mode == AT_MOST || mode == EXACTLY) &&
getPaint().breakText(source, 0, source.length() - 1, true, size,
null) != source.length()) {
needsEllipsizing = true;
FontMetricsInt font = getPaint().getFontMetricsInt();
int height = Math.abs(font.top - font.bottom);
super.onMeasure(MeasureSpec.makeMeasureSpec(size, EXACTLY),
MeasureSpec.makeMeasureSpec(height, EXACTLY));
} else {
needsEllipsizing = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
@@ -89,20 +57,6 @@ public class EmojiTextView extends TextView {
if (size > drawingCacheSize) {
setLayerType(LAYER_TYPE_NONE, null);
}
if (changed) setTextEllipsized(source);
super.onLayout(changed, left, top, right, bottom);
}
@Nullable
public CharSequence ellipsize(@Nullable CharSequence text) {
if (TextUtils.isEmpty(text) || getWidth() == 0 ||
getEllipsize() != END) {
return text;
} else {
return TextUtils.ellipsize(text, getPaint(),
getWidth() - getPaddingRight() - getPaddingLeft(), END);
}
}
}

View File

@@ -32,7 +32,7 @@ public class RecentEmojiPageModel implements EmojiPageModel {
private static final Logger LOG =
Logger.getLogger(RecentEmojiPageModel.class.getName());
private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent";
private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent2";
private static final int EMOJI_LRU_SIZE = 50;
private final LinkedHashSet<String> recentlyUsed; // UI thread
@@ -98,12 +98,12 @@ public class RecentEmojiPageModel implements EmojiPageModel {
}
private String serialize(LinkedHashSet<String> emojis) {
return StringUtils.join(emojis, ";");
return StringUtils.join(emojis, "\t");
}
private LinkedHashSet<String> deserialize(@Nullable String serialized) {
if (serialized == null) return new LinkedHashSet<>();
String[] list = serialized.split(";");
String[] list = serialized.split("\t");
LinkedHashSet<String> result = new LinkedHashSet<>(list.length);
Collections.addAll(result, list);
return result;

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@@ -7,7 +7,7 @@
<path
android:fillColor="#ffa500"
android:pathData="M0,8.88178e-16 L30,8.88178e-16 L30,30 L0,30 L0,8.88178e-16 Z"/>
android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z"/>
<path
android:fillColor="#ffffff"
android:pathData="M8.9322,18.0339 C10.6078,18.0339,11.9661,19.3922,11.9661,21.0678

View File

@@ -12,9 +12,9 @@
android:fillColor="#87c214"
android:pathData="M64.9004,0 C55.2004,0,47.1992,7.99922,47.1992,17.6992 L47.1992,40.1992
L90.8008,40.1992 L90.8008,17.6992
C90.8008,7.99922,82.8992,-4.73695e-15,73.1992,0 L64.9004,0 Z M161.9,0
C90.8008,7.99922,82.8992,0,73.1992,0 L64.9004,0 Z M161.9,0
C152.2,0,144.199,7.99922,144.199,17.6992 L144.199,137.199 L187.801,137.199
L187.801,17.6992 C187.801,7.99922,179.899,-4.73695e-15,170.199,0 L161.9,0 Z
L187.801,17.6992 C187.801,7.99922,179.899,0,170.199,0 L161.9,0 Z
M47.1992,97.8008 L47.1992,217.301 C47.1992,227.001,55.1004,235,64.9004,235
L73.1992,235 C82.8992,235,90.9004,227.001,90.9004,217.301 L90.9004,97.8008
L47.1992,97.8008 Z M144.199,194.801 L144.199,217.301
@@ -25,12 +25,12 @@ C179.899,235,187.9,227.001,187.9,217.301 L187.9,194.801 L144.199,194.801 Z"/>
android:pathData="M144.2,144.2 L187.9,144.2 L187.9,187.9 L144.2,187.9 L144.2,144.2 Z"/>
<path
android:fillColor="#95d220"
android:pathData="M17.6992,47.1992 C7.99922,47.1992,2.36848e-15,55.1004,0,64.9004 L0,73.1992
android:pathData="M17.6992,47.1992 C7.99922,47.1992,0,55.1004,0,64.9004 L0,73.1992
C0,82.8992,7.89922,90.9004,17.6992,90.9004 L137.199,90.9004 L137.199,47.1992
L17.6992,47.1992 Z M194.801,47.1992 L194.801,90.9004 L217.301,90.9004
C227.001,90.9004,235,82.9992,235,73.1992 L235,64.9004
C235,55.1004,227.001,47.1992,217.301,47.1992 L194.801,47.1992 Z M17.6992,144.199
C7.99922,144.199,2.36848e-15,152.1,0,161.9 L0,170.199
C7.99922,144.199,0,152.1,0,161.9 L0,170.199
C0,179.899,7.89922,187.9,17.6992,187.9 L40.1992,187.9 L40.1992,144.199
L17.6992,144.199 Z M97.8008,144.199 L97.8008,187.9 L217.301,187.9
C227.001,187.9,235,179.899,235,170.199 L235,161.9

View File

@@ -1,44 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:padding="20dp" >
android:orientation="vertical"
android:padding="@dimen/margin_large">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="@dimen/text_size_medium"
android:text="@string/choose_forum_name" />
<EditText
<android.support.design.widget.TextInputLayout
android:id="@+id/createForumNameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/createForumNameEntry"
android:maxLines="1"
android:inputType="text|textCapSentences" />
app:errorEnabled="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/createForumFeedback"
android:gravity="center"
android:paddingLeft="50dp"
android:paddingRight="50dp" />
<EditText
android:id="@+id/createForumNameEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/choose_forum_hint"
android:inputType="text|textCapSentences"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<Button
style="@style/BriarButton"
android:id="@+id/createForumButton"
android:text="@string/create_forum_button" />
style="@style/BriarButton"
android:enabled="false"
android:text="@string/create_forum_button"/>
<ProgressBar
android:id="@+id/createForumProgressBar"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show More