Compare commits

...

135 Commits

Author SHA1 Message Date
akwizgran
68738a5a03 Refactor layouts for alias and link dialogs. 2018-10-31 15:28:58 +00:00
Torsten Grote
1a025d0f40 [android] Show existing alias in alias edit text view
This commit also uses LiveData Transformations to expose contact related information
2018-10-30 08:36:10 -03:00
Torsten Grote
a3593ea8ca [android] Show contact alias inside private groups and their memberlist 2018-10-29 19:41:01 -03:00
Torsten Grote
34eaedbd63 Show alias for introduction notices in private conversation 2018-10-29 19:23:22 -03:00
Torsten Grote
44e1ce32ce [android] Show alias for creator of private group in list of private groups 2018-10-29 18:38:20 -03:00
Torsten Grote
7af4b3d3ca [android] Show Author alias in AuthorView 2018-10-29 18:18:05 -03:00
Torsten Grote
1423ca7a15 [android] Add UI for changing and displaying contact alias
Note that this commit only shows the alias where the Contact is
available. In cases, where we only have the Author, only its name is
shown.

This also introduces the first ViewModel to share state between the
ConversationActivity and the AliasDialogFragment.
2018-10-29 18:18:05 -03:00
Torsten Grote
baf64e1129 [bramble] Add transactionless method for retrieving AuthorInfo to ContactManager 2018-10-29 18:16:34 -03:00
Torsten Grote
88adfabe09 Refactor Author.Status into dedicated AuthorInfo class and add alias 2018-10-29 17:23:45 -03:00
Torsten Grote
969150bff0 [bramble] Factor out database type placeholder replacement
to make it available in database schema migrations
2018-10-29 12:50:08 -03:00
Torsten Grote
8fc622f85d [bramble] Add support for contact aliases
Foundation for #41
2018-10-29 12:50:08 -03:00
akwizgran
22eed91019 Merge branch 'javalin-access-manager' into 'master'
[headless] Set up access manager before starting server

See merge request briar/briar!966
2018-10-29 15:35:48 +00:00
akwizgran
fcb88ed58c Merge branch '1147-bluetooth-discovery' into 'master'
Support Bluetooth discovery for adding contacts

See merge request briar/briar!954
2018-10-29 14:35:17 +00:00
Torsten Grote
0d940fc7d7 [headless] Set up access manager before starting server
This became necesary to due an upstream change we missed when bumping
the dependency:

ab19ff91b7
2018-10-29 11:20:48 -03:00
akwizgran
53da13794f Merge branch '1422-activity-log' into 'master'
Log when activities start and stop

See merge request briar/briar!959
2018-10-24 16:00:52 +00:00
akwizgran
2ab03f48cc Merge branch '1256-remove-contact' into 'master'
briar-headless: Add endpoint for removing a contact

See merge request briar/briar!962
2018-10-24 15:59:39 +00:00
Torsten Grote
436f45554d [briar-headless] update dependencies 2018-10-24 12:41:29 -03:00
Torsten Grote
51209b5eec briar-headless: Add endpoint for removing a contact 2018-10-24 12:12:33 -03:00
akwizgran
822597b4c6 Merge branch '1373-mirror-icons' into 'master'
Fix RTL icon mirroring in DevReportActivity

Closes #1373

See merge request briar/briar!960
2018-10-24 14:05:06 +00:00
akwizgran
7c01bc59c0 Merge branch '1252-dark-theme-system-default' into 'master'
Remove system default theme option on API < 28

Closes #1252

See merge request briar/briar!961
2018-10-24 13:52:20 +00:00
Torsten Grote
825d342f9b Remove system default theme option on API < 27
Closes #1252
2018-10-24 10:40:43 -03:00
Torsten Grote
34955fecbb Fix RTL icon mirroring in DevReportActivity
For some reason, the toolbar icon has a wrong layout direction,
so the autoMirrored attribute doesn't take any effect.
2018-10-22 16:53:17 -03:00
Torsten Grote
5c28b60a6b Log when activities start and stop
Remove BriarRecyclerView log messages
2018-10-22 14:11:36 -03:00
akwizgran
389b2b5b8e Merge branch '1423-check-lifecycle-state' into 'master'
Check lifecycle state before changing fragment

Closes #1423

See merge request briar/briar!958
2018-10-22 14:27:30 +00:00
Torsten Grote
78abfa3698 Check lifecycle state before changing fragment 2018-10-19 16:40:00 -03:00
akwizgran
9c4fb4fd34 Remove unused string. 2018-10-18 17:22:54 +01:00
akwizgran
3d6a336f6d Refactor permissions code, add comments, fix corner cases. 2018-10-18 17:16:49 +01:00
akwizgran
e47d41596a Merge branch '1254-inject-object-mapper' into 'master'
briar-headless: Inject a singleton ObjectMapper for JSON parsing

See merge request briar/briar!957
2018-10-18 15:40:20 +00:00
Torsten Grote
8cf54bcedb briar-headless: Inject a singleton ObjectMapper for JSON parsing 2018-10-18 09:55:34 -03:00
akwizgran
89d5145665 Bump version numbers for 1.1.3 release. 2018-10-17 17:51:11 +01:00
Torsten Grote
0706498b03 Merge branch 'upgrade-android-gradle-plugin' into 'master'
Upgrade Android Gradle plugin to 3.2.1.

See merge request briar/briar!956
2018-10-17 16:36:24 +00:00
akwizgran
b296500e7a Update translations. 2018-10-17 12:41:24 +01:00
akwizgran
60a8b03344 Upgrade Android Gradle plugin to 3.2.1. 2018-10-17 12:34:13 +01:00
Torsten Grote
ae16a93522 Merge branch 'compact-at-startup' into 'master'
Compact the database at startup

See merge request briar/briar!955
2018-10-16 16:02:40 +00:00
Torsten Grote
c9a2ff71ae Merge branch 'tor-v3-hidden-service-addresses' into 'master'
Add support for v3 hidden service addresses

See merge request briar/briar!952
2018-10-16 15:55:34 +00:00
akwizgran
16f4c60a56 Add test for compaction time. 2018-10-16 11:39:06 +01:00
akwizgran
76121eb871 Always compact the DB if migrations have been applied. 2018-10-16 11:24:47 +01:00
akwizgran
47c91a96ae Compact the database at startup. 2018-10-16 11:13:37 +01:00
akwizgran
14befb957b Add support for v3 hidden service addresses. 2018-10-16 10:15:18 +01:00
akwizgran
4b7a81177c Static imports. 2018-10-15 14:46:40 +01:00
Torsten Grote
b464fe1653 Merge branch 'message-terminology' into 'master'
Use "text" to refer to message text

See merge request briar/briar!948
2018-10-15 13:22:31 +00:00
akwizgran
09c6f09805 Merge branch '1420-tor-status-update' into 'master'
Disable TorPlugin before applying changed settings

Closes #1420

See merge request briar/briar!953
2018-10-15 12:54:41 +00:00
Torsten Grote
a93093182d Disable TorPlugin before applying changed settings
The is necessary for two reasons:

1. Since Tor 0.3.4.8, it doesn't reconnect when changing the value of
   UseBridges via the control connection without also resetting
   DisableNetwork
2. The TorPlugin needs to set itself to a disconnected state for the UI
   to properly reflect this new state

Fixes #1420
2018-10-15 09:43:12 -03:00
akwizgran
9515e93857 Cancel discovery after 10 seconds and try to connect. 2018-10-15 11:04:46 +01:00
akwizgran
efe15df940 Remove static import of R's fields. 2018-10-15 11:04:46 +01:00
akwizgran
de611857cf Discover BT devices if no address is provided. 2018-10-15 11:04:46 +01:00
akwizgran
8935ec2c2e Don't wait for state change if BT is already discoverable. 2018-10-15 11:04:45 +01:00
akwizgran
bd00fb1c04 Ask for coarse location permission before adding a contact. 2018-10-15 11:04:45 +01:00
akwizgran
3192015cfd Ask for Bluetooth discoverability before adding a contact. 2018-10-15 11:04:45 +01:00
Torsten Grote
e776ee02b0 Merge branch 'tor-0.3.4.8' into 'master'
Upgrade Tor to 0.3.4.8

See merge request briar/briar!951
2018-10-11 22:44:45 +00:00
akwizgran
c0553ec11f Upgrade Linux Tor binaries to 0.3.4.8. 2018-10-11 15:05:39 +01:00
akwizgran
75a871a2f8 Upgrade Android Tor binaries to 0.3.4.8. 2018-10-11 14:38:18 +01:00
akwizgran
d6d3d5acef Merge branch '1240-no-empty-messages' into 'master'
Don't allow empty message bodies

See merge request briar/briar!949
2018-10-10 14:52:34 +00:00
Torsten Grote
a361a2613c Merge branch 'test-configurations' into 'master'
Update test configurations for Android Studio 3.2

See merge request briar/briar!950
2018-10-10 12:38:19 +00:00
akwizgran
b68dbd6a75 Merge branch 'upgrade-dependencies' into 'master'
Upgrade some of the things

See merge request briar/briar!935
2018-10-10 11:42:45 +00:00
akwizgran
f1e89a3ff4 Don't allow empty message bodies. 2018-10-10 10:57:41 +01:00
akwizgran
056c23167d Update test configurations for Android Studio 3.2. 2018-10-10 10:43:07 +01:00
akwizgran
79d5612645 Use "text" to refer to message text. 2018-10-10 10:40:30 +01:00
akwizgran
a030f92275 Merge branch 'headless' into 'master'
Add Briar headless client that exposes a REST API

See merge request briar/briar!931
2018-10-09 15:43:31 +00:00
Torsten Grote
b3615b4a77 briar-headless: Last round of review comments 2018-10-09 12:19:21 -03:00
akwizgran
8a15fb242a Merge branch 'briar-integration-test-transaction' into 'master'
Remove custom DB transaction code from BriarIntegrationTest

See merge request briar/briar!946
2018-10-09 14:16:23 +00:00
Torsten Grote
e3686186ee Fix closing server with ^C 2018-10-08 18:40:21 -03:00
akwizgran
18ae388137 Merge branch '1395-low-memory-crash' into 'master'
Don't use non-AppCompat theme for AppCompat activities

Closes #1395

See merge request briar/briar!947
2018-10-08 12:03:20 +00:00
Torsten Grote
775031e893 Don't use non-AppCompat theme for AppCompat activities
Fixes #1395
2018-10-05 16:50:11 -03:00
Torsten Grote
9f91b91a4f Remove custom DB transaction code from BriarIntegrationTest 2018-10-05 15:41:29 -03:00
Torsten Grote
280f3ba1fc briar-headless: POST text as JSON in body instead of form parameter 2018-10-05 15:23:31 -03:00
Torsten Grote
66619fd3a4 briar-headless: Next round of review comments 2018-10-05 15:23:31 -03:00
akwizgran
c7eb0cbb6d Include body of private request if present. 2018-10-05 15:23:31 -03:00
akwizgran
1617a95bb9 Only include "body" for headers that can have bodies. 2018-10-05 15:23:31 -03:00
Torsten Grote
6f54718756 Use short type labels in JSON API instead of long Java-like namespaces 2018-10-05 15:23:31 -03:00
Torsten Grote
ea749f2128 Minor improvements to JsonDict output classes 2018-10-05 15:23:31 -03:00
akwizgran
b4b0d3daa6 Allow null values in JsonDict. 2018-10-05 15:23:31 -03:00
akwizgran
609c90f57e Convert Map#put() to assignment. 2018-10-05 15:23:31 -03:00
akwizgran
5cf68fa134 Use JsonDict for blog post headers. 2018-10-05 15:23:31 -03:00
akwizgran
61c9c6b8eb Add visitor to dispatch output() dynamically. 2018-10-05 15:23:31 -03:00
akwizgran
e97608da40 Add test to show that static dispatch won't work. 2018-10-05 15:23:31 -03:00
akwizgran
0bb80b1a15 Add JsonDict class for JSON output. 2018-10-05 15:23:31 -03:00
akwizgran
bda52ea548 Use maps for JSON output. 2018-10-05 15:23:31 -03:00
Torsten Grote
cf033dc29d briar-headless: Address second round of review comments 2018-10-05 15:23:31 -03:00
Torsten Grote
c12cedc371 briar-headless: Address first round of review comments 2018-10-05 15:23:31 -03:00
Torsten Grote
4b5e9bd64f Ensure the use SecureRandom when creating authentication token 2018-10-05 15:23:31 -03:00
Torsten Grote
8d55911dab Add unit test for WebSocketController
Also move the controller into an event package
2018-10-05 15:23:31 -03:00
Torsten Grote
e381f83512 Last code cleanup before submitting merge request 2018-10-05 15:23:31 -03:00
Torsten Grote
e4c7f13832 Add a README.md with API documentation
Also fix some smaller issues found during writing the documentation
2018-10-05 15:23:31 -03:00
Torsten Grote
b089a204d3 Add support for websocket authentication via basic auth
The token should be used as username and the password left empty
2018-10-05 15:23:31 -03:00
Torsten Grote
85fcb34997 Add briar-headless Android Studio run configuration 2018-10-05 15:23:31 -03:00
Torsten Grote
d6d132a9cf Add Bearer Authentication to REST API 2018-10-05 15:23:31 -03:00
Torsten Grote
98d1ea7730 briar-headless: Add more controller tests
Current controller line coverage: 100%
2018-10-05 15:23:31 -03:00
Torsten Grote
159fd34c0c Use Conversation Manager for message retrieval 2018-10-05 15:23:31 -03:00
Torsten Grote
9e7a387ea4 Turn output classes into Kotlin data classes 2018-10-05 15:23:31 -03:00
Torsten Grote
138e520e6c briar-headless: Add command line arguments 2018-10-05 15:23:31 -03:00
Torsten Grote
5783c1dfd8 briar-headless: Add a websocket controller for private message events
Also version API URLs
2018-10-05 15:23:31 -03:00
Torsten Grote
348968018a Migrate REST classes to Kotlin and upgrade Javalin 2018-10-05 15:23:31 -03:00
Torsten Grote
33c509cd1f briar-headless: Add Kotlin and first unit test for blogs with Mockk 2018-10-05 15:23:31 -03:00
Torsten Grote
bea77151bd briar-headless: Add API to list all contacts 2018-10-05 15:23:31 -03:00
Torsten Grote
787e62345f Add simple MessagingController 2018-10-05 15:23:31 -03:00
Torsten Grote
48f6a3b91f Add Tor plugin to headless client and introduce new ConfigurationManager 2018-10-05 15:23:31 -03:00
Torsten Grote
a798e25bf2 Save app data always in $HOME/.briar for now 2018-10-05 15:23:31 -03:00
Torsten Grote
31e4045cf7 Try to shutdown cleanly when server stops or SIGINT is received 2018-10-05 15:23:31 -03:00
Torsten Grote
5334a8c9ca Add basic support for listing and writing blog posts 2018-10-05 15:23:31 -03:00
Torsten Grote
d11f1d2805 Add a poor man's argument to explicitly turn on verbose logging 2018-10-05 15:23:31 -03:00
Torsten Grote
0d1ebddcd2 Allow account creation and reading password from STDIN 2018-10-05 15:23:31 -03:00
Torsten Grote
6c296c1348 Proof-of-Concept Headless Client 2018-10-05 15:23:31 -03:00
akwizgran
87701e5f07 Merge branch 'transactional-db' into 'master'
Transactional DB interface

See merge request briar/briar!945
2018-10-05 15:19:40 +00:00
Torsten Grote
3aae01d152 Merge branch 'panic-app-list' into 'master'
Update list of panic button apps after installing app

Closes #1392

See merge request briar/briar!940
2018-10-05 14:18:44 +00:00
akwizgran
bc298ba68a Remove unnecessary final modifiers. 2018-10-05 09:34:41 +01:00
akwizgran
2623eaa149 Remove unnecessary throwing variants. 2018-10-05 09:32:01 +01:00
akwizgran
7359b6942a Use transactional DB interface for ForumManagerImpl. 2018-10-04 15:59:10 +01:00
akwizgran
3bcc532b4b Add transactional DB interface. 2018-10-04 15:56:21 +01:00
akwizgran
4d08c69779 Revert spurious changes to run configurations. 2018-10-03 13:08:00 +01:00
Torsten Grote
a6cd8937f7 Remove space reserved for icons from preferences
Works around https://issuetracker.google.com/issues/111907042

Might be replacable with a solution from
https://issuetracker.google.com/issues/111907042 in the future.
2018-10-03 12:58:49 +01:00
akwizgran
e8566906ef Update gradle-witness to exclude android.jar from dependencies. 2018-10-03 12:48:29 +01:00
akwizgran
929102ed60 Upgrade build tools to 28.0.3. 2018-10-03 12:48:29 +01:00
akwizgran
3b871f5932 Update ProGuard rules for new OkHttp version. 2018-10-03 12:48:29 +01:00
akwizgran
b972d1fc13 Update ACRA usage for new version. 2018-10-03 12:48:28 +01:00
akwizgran
ccbeee60a7 Upgrade Rome, OkHttp and jsoup libraries. 2018-10-03 12:48:28 +01:00
akwizgran
074b10e177 Upgrade JNA library. 2018-10-03 12:48:28 +01:00
akwizgran
031516ccce Upgrade curve25519-java library. 2018-10-03 12:48:28 +01:00
akwizgran
7d2f1abb94 Upgrade Gradle APT and Animal Sniffer plugins. 2018-10-03 12:48:28 +01:00
akwizgran
00b9c76bb8 Upgrade ACRA. 2018-10-03 12:48:28 +01:00
akwizgran
4d9fab85cb Upgrade zxing, material tap target libraries. 2018-10-03 12:48:28 +01:00
akwizgran
bd2514a299 Upgrade support library to 28.0.0. 2018-10-03 12:48:27 +01:00
akwizgran
e795efc7fc Bump compileSdkVersion for bramble-android. 2018-10-03 12:48:27 +01:00
akwizgran
6691d2164f Upgrade Gradle Android plugin and build tools. 2018-10-03 12:48:27 +01:00
Administrator
a384450c36 Merge branch '1373-format-numbers' into 'master'
Format numbers in locale as well (not all languages use the same)

See merge request briar/briar!943
2018-10-03 11:41:56 +00:00
akwizgran
b375e9873c Merge branch '1409-localize-crash-screen' into 'master'
Localize crash screen in language defined in settings

Closes #1409

See merge request briar/briar!944
2018-10-03 07:53:41 +00:00
Torsten Grote
cb30c3885a Localize crash screen in language defined in settings 2018-10-02 17:56:33 -03:00
Torsten Grote
6ee81eb24c Format numbers in locale as well (not all languages use the same)
Done according to
https://developer.android.com/training/basics/supporting-devices/languages#FormatNumbers
2018-10-02 17:31:23 -03:00
Torsten Grote
c14ebe82ce Merge branch '1365-keep-screen-on' into 'master'
Keep screen on while QR code viewfinder is open

See merge request briar/briar!942
2018-10-02 11:20:46 +00:00
Torsten Grote
00e9f894b1 Merge branch 'remove-tor-patch' into 'master'
Remove unused patches

See merge request briar/briar!941
2018-10-02 11:13:06 +00:00
akwizgran
499c586a59 Keep screen on while scanning QR code. 2018-10-02 12:03:29 +01:00
akwizgran
64f9ce7306 Remove unused patches. 2018-10-02 11:49:29 +01:00
akwizgran
071d961ed1 Remove debug logging. 2018-09-28 11:31:22 +01:00
akwizgran
cb9efc5fec Fix lint warnings. 2018-09-28 11:28:47 +01:00
akwizgran
f9e292f734 Update panic app list after installing app. 2018-09-28 11:27:08 +01:00
329 changed files with 5970 additions and 2562 deletions

View File

@@ -36,6 +36,9 @@
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
@@ -257,5 +260,11 @@
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
<option name="VARIABLE_ANNOTATION_WRAP" value="1" />
<option name="ENUM_CONSTANTS_WRAP" value="1" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All in briar-headless" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true">
<module name="briar-headless" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-headless" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -24,6 +24,7 @@
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All in briar-headless" run_configuration_type="AndroidJUnit" />
</method>
</configuration>
</component>
</component>

View File

@@ -1,6 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-android" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-android" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@@ -11,12 +10,10 @@
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-android" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>

View File

@@ -1,6 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-api" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@@ -11,12 +10,10 @@
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>

View File

@@ -1,6 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@@ -11,12 +10,10 @@
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>

View File

@@ -1,6 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-java" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-java" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@@ -11,12 +10,10 @@
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-java" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>

View File

@@ -1,6 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="briar-android" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@@ -11,12 +10,10 @@
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>

View File

@@ -1,6 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="briar-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@@ -11,12 +10,10 @@
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>

View File

@@ -1,6 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="H2 Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@@ -11,12 +10,10 @@
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>

View File

@@ -1,6 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="HyperSQL Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@@ -11,12 +10,10 @@
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="briar-headless" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="-v" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.briar.headless.MainKt" />
<option name="WORKING_DIRECTORY" value="" />
<module name="briar-headless" />
<envs />
<method>
<option name="Gradle.BeforeRunTask" enabled="true" tasks="jar" externalProjectPath="$PROJECT_DIR$/briar-headless" vmOptions="" scriptParameters="" />
</method>
</configuration>
</component>

View File

@@ -3,14 +3,14 @@ apply plugin: 'witness'
apply from: 'witness.gradle'
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 10102
versionName "1.1.2"
versionCode 10103
versionName "1.1.3"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@@ -28,7 +28,7 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.2.9.16@zip'
tor 'org.briarproject:tor-android:0.3.4.8@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'

View File

@@ -16,18 +16,27 @@ import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.AndroidUtils;
import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_FINISHED;
import static android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_STARTED;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
@@ -37,8 +46,13 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -47,8 +61,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private static final Logger LOG =
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
private static final int MAX_DISCOVERY_MS = 10_000;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final Clock clock;
private volatile boolean wasEnabledByUs = false;
private volatile BluetoothStateReceiver receiver = null;
@@ -58,12 +75,13 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
Context appContext, SecureRandom secureRandom, Clock clock,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
maxLatency);
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.clock = clock;
}
@Override
@@ -182,6 +200,74 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
}
}
@Override
@Nullable
DuplexTransportConnection discoverAndConnect(String uuid) {
if (adapter == null) return null;
for (String address : discoverDevices()) {
try {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
return connectTo(address, uuid);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to "
+ scrubMacAddress(address));
}
}
}
LOG.info("Could not connect to any devices");
return null;
}
private Collection<String> discoverDevices() {
List<String> addresses = new ArrayList<>();
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
DiscoveryReceiver receiver = new DiscoveryReceiver(intents);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCOVERY_STARTED);
filter.addAction(ACTION_DISCOVERY_FINISHED);
filter.addAction(ACTION_FOUND);
appContext.registerReceiver(receiver, filter);
try {
if (adapter.startDiscovery()) {
long now = clock.currentTimeMillis();
long end = now + MAX_DISCOVERY_MS;
while (now < end) {
Intent i = intents.poll(end - now, MILLISECONDS);
if (i == null) break;
String action = i.getAction();
if (ACTION_DISCOVERY_STARTED.equals(action)) {
LOG.info("Discovery started");
} else if (ACTION_DISCOVERY_FINISHED.equals(action)) {
LOG.info("Discovery finished");
break;
} else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
String address = d.getAddress();
if (LOG.isLoggable(INFO))
LOG.info("Discovered " + scrubMacAddress(address));
if (!addresses.contains(address))
addresses.add(address);
}
now = clock.currentTimeMillis();
}
} else {
LOG.info("Could not start discovery");
}
} catch (InterruptedException e) {
LOG.info("Interrupted while discovering devices");
Thread.currentThread().interrupt();
} finally {
LOG.info("Cancelling discovery");
adapter.cancelDiscovery();
appContext.unregisterReceiver(receiver);
}
// Shuffle the addresses so we don't always try the same one first
Collections.shuffle(addresses);
return addresses;
}
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
@@ -207,4 +293,18 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
}
}
}
private static class DiscoveryReceiver extends BroadcastReceiver {
private final BlockingQueue<Intent> intents;
private DiscoveryReceiver(BlockingQueue<Intent> intents) {
this.intents = intents;
}
@Override
public void onReceive(Context ctx, Intent intent) {
intents.add(intent);
}
}
}

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
@@ -33,17 +34,19 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final Context appContext;
private final SecureRandom secureRandom;
private final EventBus eventBus;
private final Clock clock;
private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor,
AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus,
SecureRandom secureRandom, EventBus eventBus, Clock clock,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.secureRandom = secureRandom;
this.eventBus = eventBus;
this.clock = clock;
this.backoffFactory = backoffFactory;
}
@@ -65,7 +68,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, ioExecutor, androidExecutor, appContext,
secureRandom, backoff, callback, MAX_LATENCY);
secureRandom, clock, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -1,40 +1,42 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.android.tools.analytics-library:protos:26.1.3:protos-26.1.3.jar:818c9f256f141d9dafec03a1aa2b94d240b2c140acfd7ee31a8b3e6c2b9479e3',
'com.android.tools.analytics-library:shared:26.1.3:shared-26.1.3.jar:7110706c7ada96c8b6f5ca80c478291bc7899d46277de2c48527e045442401a3',
'com.android.tools.analytics-library:tracker:26.1.3:tracker-26.1.3.jar:4155424bf2ce4872da83332579a1707252bc66cbd77c5144fdc4483d0f2e1418',
'com.android.tools.build:apksig:3.1.3:apksig-3.1.3.jar:7e1f8e675a6e768e5b56405e41d6c3cc05befe62e601b04177de1029902c9c89',
'com.android.tools.build:builder-model:3.1.3:builder-model-3.1.3.jar:06ad1c422d679fc698451479cb40ba863849d67bfd1de23f6d2c16d78b024b0b',
'com.android.tools.build:builder-test-api:3.1.3:builder-test-api-3.1.3.jar:4d989f780436794f0f8b2f50e9e079b786571eac90f26c208ab2ae6d4012f389',
'com.android.tools.build:builder:3.1.3:builder-3.1.3.jar:8a1092012c89d0ec1ee2eff09c5708c71ef4482a6862df8d3a44a67fccace01c',
'com.android.tools.build:gradle-api:3.1.3:gradle-api-3.1.3.jar:01e4df521456aef66514336f1d492346730dd1fb8f6433a89f62da834941ed72',
'com.android.tools.build:manifest-merger:26.1.3:manifest-merger-26.1.3.jar:1e4fc7e932adb4607082409800e5e6fccb42e6c5360ae5990094bf522f3ada55',
'com.android.tools.ddms:ddmlib:26.1.3:ddmlib-26.1.3.jar:c54931cd68df5d1ea2923b3b320eae47cd2307a5a916bb8674c0acf93cd1d3cd',
'com.android.tools.external.com-intellij:intellij-core:26.1.3:intellij-core-26.1.3.jar:af67f5535fef2e1a28b1007a4acb8c5deb6a1e33b8afe7b11d012c9e778ebcec',
'com.android.tools.external.com-intellij:kotlin-compiler:26.1.3:kotlin-compiler-26.1.3.jar:c746d2859dc11cc05c84b692b3498d3a621e0929511f8440ee009c6557838fd4',
'com.android.tools.external.org-jetbrains:uast:26.1.3:uast-26.1.3.jar:3f3f6651d0c7685a77ecb22e9c82d6b49fdf24322c17360768dc530678f43265',
'com.android.tools.layoutlib:layoutlib-api:26.1.3:layoutlib-api-26.1.3.jar:10bc73ce706c45629872d6a999dbe12116df64e24f47ff93b7b13121ff57b4b0',
'com.android.tools.lint:lint-api:26.1.3:lint-api-26.1.3.jar:6f97323f9af8deda86278717885b5c927f3766757db89709f52d11d42b6fb751',
'com.android.tools.lint:lint-checks:26.1.3:lint-checks-26.1.3.jar:73c3d53784c9ce3e6d5968506581918e0179645d20809927ca4a001dd766b001',
'com.android.tools.lint:lint-gradle-api:26.1.3:lint-gradle-api-26.1.3.jar:7ca3c4866ec21dc21d53a9d86f752b77ace6f6c610a0c9dc877313856c733d9d',
'com.android.tools.lint:lint-gradle:26.1.3:lint-gradle-26.1.3.jar:db0c354b8f4b6f6637e31f91c564785a59ff896325331fcbc3de7458e0b6c067',
'com.android.tools.lint:lint-kotlin:26.1.3:lint-kotlin-26.1.3.jar:94e2b0f4565a241561cfb8fc1222bb3f132a3b98d2a90421dbb72ee8358e7d68',
'com.android.tools.lint:lint:26.1.3:lint-26.1.3.jar:8d5f32c989c6d191d712e90ad3ca2d1c409313599551d04d834caa44d26c78df',
'com.android.tools:annotations:26.1.3:annotations-26.1.3.jar:c950430b24ac5d58fc97e7283b8f0115f99587e76e08b4e1e2aaa780f2d77323',
'com.android.tools:common:26.1.3:common-26.1.3.jar:7c31a90581a148ab219f615a59667f0dded7fa39b248529784474da3c2274ef2',
'com.android.tools:dvlib:26.1.3:dvlib-26.1.3.jar:0cae87906f53d3f1088366a916ed180a7312b6d9919b90797f238875c8492855',
'com.android.tools:repository:26.1.3:repository-26.1.3.jar:52d4539cc68db91b261e2a33b2c8206b26e05539078758dc28cfb3854adb4f59',
'com.android.tools:sdk-common:26.1.3:sdk-common-26.1.3.jar:1948603ca9ff22c7ebb3178000bffa3a9dd2ca1cc5cb0c793cae08468b8fcfc1',
'com.android.tools:sdklib:26.1.3:sdklib-26.1.3.jar:4adcfaad9514607098d2c51503c39811112d3050f4d1e744c01c7f08f591032b',
'com.android.tools.analytics-library:protos:26.2.1:protos-26.2.1.jar:2f371f5b1f551e85ab08be4d6a2873471b3d44afd1ebf6aa3298f3b796bf691f',
'com.android.tools.analytics-library:shared:26.2.1:shared-26.2.1.jar:4c1e4e705fa4d45f23aaea230557f6508155012d9c296337787c1d7b26a97f5a',
'com.android.tools.analytics-library:tracker:26.2.1:tracker-26.2.1.jar:4a624ecc976539f755ddb0bb8dfc2dd3d08326cfec59a098dbd70f701ca7fb75',
'com.android.tools.build:aapt2:3.2.1-4818971:aapt2-3.2.1-4818971-linux.jar:f431b6f96c91a2c155144b091a9c97d9805c589fe8efc9c930b6cd346cb60a1e',
'com.android.tools.build:apksig:3.2.1:apksig-3.2.1.jar:2b46f2feffea66037aab29e4261b2433c190194a6ef97b958511eb157f2ccba5',
'com.android.tools.build:apkzlib:3.2.1:apkzlib-3.2.1.jar:c39ad0313905932431fe81c8899c2cf39a4d92ad6c4edcaa4b25432f461452aa',
'com.android.tools.build:builder-model:3.2.1:builder-model-3.2.1.jar:a9f68e6abcec122f9cb5ad352d3f05a3eb03acbcdca95e4d25c16310c2c965ff',
'com.android.tools.build:builder-test-api:3.2.1:builder-test-api-3.2.1.jar:533ac6c2b5884bb54967a33791f2628dfdfac7981af39417a333b43d4379b6be',
'com.android.tools.build:builder:3.2.1:builder-3.2.1.jar:aedcbfd115dbe91d09b4113e66ef50589b558d0aa3b2f133b1d867c9b87fae83',
'com.android.tools.build:gradle-api:3.2.1:gradle-api-3.2.1.jar:57cf0ac5ac1dca8afdb3f62b94265e776e7dcfa641cc3844fb53a05193de208d',
'com.android.tools.build:manifest-merger:26.2.1:manifest-merger-26.2.1.jar:8830573263361035d38cfdcb51e2db94029c93865b21334f5fbf8a27984281a6',
'com.android.tools.ddms:ddmlib:26.2.1:ddmlib-26.2.1.jar:a4bf0a29a19980bf27269465cc782064656750b77c26728f82f9e148b705218b',
'com.android.tools.external.com-intellij:intellij-core:26.2.1:intellij-core-26.2.1.jar:4925ad1892c2687cb1a63427d440ef519c8c59215fefe0dc5d541d5d411fcafe',
'com.android.tools.external.com-intellij:kotlin-compiler:26.2.1:kotlin-compiler-26.2.1.jar:daa064fd708f340ee25fb9823c4c74104ac77f1370b76d907eb9ae6daec0a2ae',
'com.android.tools.external.org-jetbrains:uast:26.2.1:uast-26.2.1.jar:f10f7258d2ab9189562cc0f9ad838c0378fdba439229173390a99de02ebac75b',
'com.android.tools.layoutlib:layoutlib-api:26.2.1:layoutlib-api-26.2.1.jar:ddbf4fca123733fa011595b1cc1f4ac2937ed327b60990711fafc33c775c2ade',
'com.android.tools.lint:lint-api:26.2.1:lint-api-26.2.1.jar:3b57e739de567b98bc9ab56c2c0ee66fc026b4adf5843e8f9804ca0666a6f66e',
'com.android.tools.lint:lint-checks:26.2.1:lint-checks-26.2.1.jar:c86f4cc9aaee722ee4ad70062f7b5af91e9b041914af27adc09f545ab0fb3bc6',
'com.android.tools.lint:lint-gradle-api:26.2.1:lint-gradle-api-26.2.1.jar:2283e7af32e301565f2a797e531f0fc8c648077d457afb3ffdddbee638976c2f',
'com.android.tools.lint:lint-gradle:26.2.1:lint-gradle-26.2.1.jar:8fd90b2f3ec788cbb9801c07ab3e1ea2255aa31a6093157d7ea0ff13d0315ecb',
'com.android.tools.lint:lint-kotlin:26.2.1:lint-kotlin-26.2.1.jar:7a6a5d2b18f69cf1b900d857c2632b4c683713c533295933b8b759f8cab4a877',
'com.android.tools.lint:lint:26.2.1:lint-26.2.1.jar:7848b82ae988b90dee259ae7c7e86e05cbf52db6cd21c8bbd38ce7df08f3f8c5',
'com.android.tools:annotations:26.2.1:annotations-26.2.1.jar:7391c6a1e080174b96e64ceb078dadd31ce4d8a2d2fee0ec65be202126f90f24',
'com.android.tools:common:26.2.1:common-26.2.1.jar:a50aab2d6411ff68f4004a87c7e93d87d8e980a0ec3b352246549897ea2d78e5',
'com.android.tools:dvlib:26.2.1:dvlib-26.2.1.jar:72a83bf2839b1df9b1fbf67ba45d1bfb9f966cd774da4320c762b2be8f1688aa',
'com.android.tools:repository:26.2.1:repository-26.2.1.jar:fa74dae09103faef703df38550ad8fa244c5b6d1bf90d6198be932292b3d9cc1',
'com.android.tools:sdk-common:26.2.1:sdk-common-26.2.1.jar:759d4b292ca69a35cf961fca377b54158fc6c88108978006999442e80a011cf4',
'com.android.tools:sdklib:26.2.1:sdklib-26.2.1.jar:248df7ad5eac4aeb6f96c394c76760de4b7b89ac056e54d0c21a739368b91b45',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.google.code.gson:gson:2.7:gson-2.7.jar:2d43eb5ea9e133d2ee2405cc14f5ee08951b8361302fdd93494a3a997b508d32',
'com.google.code.gson:gson:2.8.0:gson-2.8.0.jar:c6221763bd79c4f1c3dc7f750b5f29a0bb38b367b81314c4f71896e340c40825',
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.errorprone:error_prone_annotations:2.0.18:error_prone_annotations-2.0.18.jar:cb4cfad870bf563a07199f3ebea5763f0dec440fcda0b318640b1feaa788656b',
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
'com.google.guava:guava:22.0:guava-22.0.jar:1158e94c7de4da480873f0b4ab4a1da14c0d23d4b1902cc94a58a6f0f9ab579e',
'com.google.guava:guava:23.0:guava-23.0.jar:7baa80df284117e5b945b19b98d367a85ea7b7801bd358ff657946c3bd1b6596',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:40ceb7157feb263949e0f503fe5f71689333a621021aa20ce0d0acee3badaa0f',
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.4.0:protobuf-java-3.4.0.jar:dce7e66b32456a1b1198da0caff3a8acb71548658391e798c79369241e6490a4',
@@ -43,8 +45,8 @@ dependencyVerification {
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4',
'com.sun.xml.fastinfoset:FastInfoset:1.2.13:FastInfoset-1.2.13.jar:27a77db909f3c2833c0b1a37c55af1db06045118ad2eed96ce567b6632bce038',
'commons-codec:commons-codec:1.6:commons-codec-1.6.jar:54b34e941b8e1414bd3e40d736efd3481772dc26db3296f6aa45cec9f6203d86',
'commons-logging:commons-logging:1.1.1:commons-logging-1.1.1.jar:ce6f913cad1f0db3aad70186d65c5bc7ffcc9a99e3fe8e0b137312819f7c362f',
'commons-codec:commons-codec:1.9:commons-codec-1.9.jar:ad19d2601c3abf0b946b5c3a4113e226a8c1e3305e395b90013b78dd94a723ce',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
@@ -55,13 +57,13 @@ dependencyVerification {
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.httpcomponents:httpclient:4.2.6:httpclient-4.2.6.jar:362e9324ee7c697e21279e20077b52737ddef3f1b2c1a7abe5ad34b465145550',
'org.apache.httpcomponents:httpcore:4.2.5:httpcore-4.2.5.jar:e5e82da4cc66c8d917bbf743e3c0752efe8522735e7fc9dbddb65bccea81cfe9',
'org.apache.httpcomponents:httpmime:4.1:httpmime-4.1.jar:31629566148e8a47688ae43b420abc3ecd783ed15b33bebc00824bf24c9b15aa',
'org.apache.httpcomponents:httpclient:4.5.2:httpclient-4.5.2.jar:0dffc621400d6c632f55787d996b8aeca36b30746a716e079a985f24d8074057',
'org.apache.httpcomponents:httpcore:4.4.5:httpcore-4.4.5.jar:64d5453874cab7e40a7065cb01a9a9ca1053845a9786b478878b679e0580cec3',
'org.apache.httpcomponents:httpmime:4.5.2:httpmime-4.5.2.jar:231a3f7e4962053db2be8461d5422e68fc458a3a7dd7d8ada803a348e21f8f07',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:tor-android:0.2.9.16:tor-android-0.2.9.16.zip:515e33dda6a30853c885a2de2c79ae1ab9ad8b6db44f5db8890333ec2e24f4ae',
'org.briarproject:tor-android:0.3.4.8:tor-android-0.3.4.8.zip:989a0352d9d8d8172cd6c2137654e165e5d2beb10ed1211bab3814e224ad1926',
'org.codehaus.groovy:groovy-all:2.4.12:groovy-all-2.4.12.jar:6a56af4bd48903d56bec62821876cadefafd007360cc6bd0d8f7aa8d72b38be4',
'org.codehaus.mojo:animal-sniffer-annotations:1.14:animal-sniffer-annotations-1.14.jar:2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d',
'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa',
@@ -70,9 +72,10 @@ dependencyVerification {
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926',
'org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.0:kotlin-stdlib-jre8-1.2.0.jar:633524eee6ef1941f7cb1dab7ee3927b0a221ceee9047aeb5515f4cbb990c82a',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.0:kotlin-stdlib-1.2.0.jar:05cfd9f5ac0b41910703a8925f7211a495909b27a2ffdd1c5106f1689aeafcd4',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.2.71:kotlin-stdlib-common-1.2.71.jar:63999687ff2fce8a592dd180ffbbf8f1d21c26b4044c55cdc74ff3cf3b3cf328',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71:kotlin-stdlib-jdk7-1.2.71.jar:b136bd61b240e07d4d92ce00d3bd1dbf584400a7bf5f220c2f3cd22446858082',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.71:kotlin-stdlib-jdk8-1.2.71.jar:ac3c8abf47790b64b4f7e2509a53f0c145e061ac1612a597520535d199946ea9',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.71:kotlin-stdlib-1.2.71.jar:4c895c270b87f5fec2a2796e1d89c15407ee821de961527c28588bb46afbc68b',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
@@ -81,11 +84,11 @@ dependencyVerification {
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.jvnet.staxex:stax-ex:1.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm-analysis:5.1:asm-analysis-5.1.jar:a34658f5c5de4b573eef21131cc32cc25f7b66407944f312b28ec2e56abb1fa9',
'org.ow2.asm:asm-commons:5.1:asm-commons-5.1.jar:97b3786e1f55e74bddf8ad102bf50e33bbcbc1f6b7fd7b36f0bbbb25cd4981be',
'org.ow2.asm:asm-tree:5.1:asm-tree-5.1.jar:c0de2bbc4cb8297419659813ecd4ed1d077ed1dd5c1f5544cc5143e493e84c10',
'org.ow2.asm:asm-util:5.1:asm-util-5.1.jar:ee032c39ae5e3cd099148fbba9a2124f9ed613e5cb93e03ee0fa8808ce364040',
'org.ow2.asm:asm-analysis:6.0:asm-analysis-6.0.jar:2f1a6387219c3a6cc4856481f221b03bd9f2408a326d416af09af5d6f608c1f4',
'org.ow2.asm:asm-commons:6.0:asm-commons-6.0.jar:f1bce5c648a96a017bdcd01fe5d59af9845297fd7b79b81c015a6fbbd9719abf',
'org.ow2.asm:asm-tree:6.0:asm-tree-6.0.jar:887998fb69727c8759e4d253f856822801e33f9fd4caa566b3ac58ee92106215',
'org.ow2.asm:asm-util:6.0:asm-util-6.0.jar:356afebdb0f870175262e5188f8709a3b17aa2a5a6a4b0340b04d4b449bca5f6',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.ow2.asm:asm:5.1:asm-5.1.jar:d2da399a9967c69f0a21739256fa79d284222c223082cacadc17372244764b54',
'org.ow2.asm:asm:6.0:asm-6.0.jar:dd8971c74a4e697899a8e95caae4ea8760ea6c486dc6b97b1795e75760420461',
]
}

View File

@@ -38,4 +38,18 @@ public abstract class StringMap extends Hashtable<String, String> {
public void putInt(String key, int value) {
put(key, String.valueOf(value));
}
public long getLong(String key, long defaultValue) {
String s = get(key);
if (s == null) return defaultValue;
try {
return Long.valueOf(s);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public void putLong(String key, long value) {
put(key, String.valueOf(value));
}
}

View File

@@ -48,12 +48,8 @@ public abstract class BdfMessageValidator implements MessageValidator {
throw new InvalidMessageException(
"Timestamp is too far in the future");
}
byte[] body = m.getBody();
if (body.length == 0) {
throw new InvalidMessageException("Message is too short");
}
try {
BdfList bodyList = clientHelper.toList(body);
BdfList bodyList = clientHelper.toList(m.getBody());
BdfMessageContext result = validateMessage(m, g, bodyList);
Metadata meta = metadataEncoder.encode(result.getDictionary());
return new MessageContext(meta, result.getDependencies());

View File

@@ -4,8 +4,12 @@ import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
@Immutable
@NotNullByDefault
public class Contact {
@@ -13,13 +17,21 @@ public class Contact {
private final ContactId id;
private final Author author;
private final AuthorId localAuthorId;
@Nullable
private final String alias;
private final boolean verified, active;
public Contact(ContactId id, Author author, AuthorId localAuthorId,
boolean verified, boolean active) {
@Nullable String alias, boolean verified, boolean active) {
if (alias != null) {
int aliasLength = toUtf8(alias).length;
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException();
}
this.id = id;
this.author = author;
this.localAuthorId = localAuthorId;
this.alias = alias;
this.verified = verified;
this.active = active;
}
@@ -36,6 +48,11 @@ public class Contact {
return localAuthorId;
}
@Nullable
public String getAlias() {
return alias;
}
public boolean isVerified() {
return verified;
}

View File

@@ -5,11 +5,14 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import javax.annotation.Nullable;
@NotNullByDefault
public interface ContactManager {
@@ -93,6 +96,12 @@ public interface ContactManager {
void setContactActive(Transaction txn, ContactId c, boolean active)
throws DbException;
/**
* Sets an alias name for the contact or unsets it if alias is null.
*/
void setContactAlias(ContactId c, @Nullable String alias)
throws DbException;
/**
* Return true if a contact with this name and public key already exists
*/
@@ -105,6 +114,16 @@ public interface ContactManager {
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
/**
* Returns the {@link AuthorInfo} for the given author.
*/
AuthorInfo getAuthorInfo(AuthorId a) throws DbException;
/**
* Returns the {@link AuthorInfo} for the given author.
*/
AuthorInfo getAuthorInfo(Transaction txn, AuthorId a) throws DbException;
interface ContactHook {
void addingContact(Transaction txn, Contact c) throws DbException;

View File

@@ -76,6 +76,19 @@ public interface DatabaseComponent {
*/
void endTransaction(Transaction txn);
/**
* Runs the given task within a transaction.
*/
<E extends Exception> void transaction(boolean readOnly,
DbRunnable<E> task) throws DbException, E;
/**
* Runs the given task within a transaction and returns the result of the
* task.
*/
<R, E extends Exception> R transactionWithResult(boolean readOnly,
DbCallable<R, E> task) throws DbException, E;
/**
* Stores a contact associated with the given local and remote pseudonyms,
* and returns an ID for the contact.
@@ -502,6 +515,12 @@ public interface DatabaseComponent {
void setContactActive(Transaction txn, ContactId c, boolean active)
throws DbException;
/**
* Sets an alias name for the contact or unsets it if alias is null.
*/
void setContactAlias(Transaction txn, ContactId c, @Nullable String alias)
throws DbException;
/**
* Sets the given group's visibility to the given contact.
*/

View File

@@ -0,0 +1,6 @@
package org.briarproject.bramble.api.db;
public interface DbCallable<R, E extends Exception> {
R call(Transaction txn) throws DbException, E;
}

View File

@@ -0,0 +1,6 @@
package org.briarproject.bramble.api.db;
public interface DbRunnable<E extends Exception> {
void run(Transaction txn) throws DbException, E;
}

View File

@@ -6,6 +6,10 @@ public interface MigrationListener {
* This is called when a migration is started while opening the database.
* It will be called once for each migration being applied.
*/
void onMigrationRun();
void onDatabaseMigration();
/**
* This is called when compaction is started while opening the database.
*/
void onDatabaseCompaction();
}

View File

@@ -16,10 +16,6 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
@NotNullByDefault
public class Author implements Nameable {
public enum Status {
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
}
/**
* The current version of the author structure.
*/

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class AuthorInfo {
public enum Status {
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES;
public boolean isContact() {
return this == UNVERIFIED || this == VERIFIED;
}
}
private final Status status;
@Nullable
private final String alias;
public AuthorInfo(Status status, @Nullable String alias) {
this.status = status;
this.alias = alias;
}
public AuthorInfo(Status status) {
this(status, null);
}
public Status getStatus() {
return status;
}
@Nullable
public String getAlias() {
return alias;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author.Status;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
@@ -37,14 +36,4 @@ public interface IdentityManager {
*/
LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
/**
* Returns the {@link Status} of the given author.
*/
Status getAuthorStatus(AuthorId a) throws DbException;
/**
* Returns the {@link Status} of the given author.
*/
Status getAuthorStatus(Transaction txn, AuthorId a) throws DbException;
}

View File

@@ -21,7 +21,7 @@ public interface KeyAgreementConstants {
/**
* The connection timeout in milliseconds.
*/
long CONNECTION_TIMEOUT = 20 * 1000;
long CONNECTION_TIMEOUT = 60_000;
/**
* The transport identifier for Bluetooth.

View File

@@ -34,7 +34,8 @@ public interface LifecycleManager {
*/
enum LifecycleState {
STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES,
RUNNING, STOPPING;
public boolean isAfter(LifecycleState state) {
return ordinal() > state.ordinal();

View File

@@ -4,7 +4,8 @@ public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor");
String PROP_ONION = "onion";
String PROP_ONION_V2 = "onion";
String PROP_ONION_V3 = "onion3";
int SOCKS_PORT = 59050;
int CONTROL_PORT = 59051;

View File

@@ -16,6 +16,7 @@ public class Message {
private final byte[] body;
public Message(MessageId id, GroupId groupId, long timestamp, byte[] body) {
if (body.length == 0) throw new IllegalArgumentException();
if (body.length > MAX_MESSAGE_BODY_LENGTH)
throw new IllegalArgumentException();
this.id = id;

View File

@@ -0,0 +1,24 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.system.Clock;
import java.util.concurrent.atomic.AtomicLong;
public class SettableClock implements Clock {
private final AtomicLong time;
public SettableClock(AtomicLong time) {
this.time = time;
}
@Override
public long currentTimeMillis() {
return time.get();
}
@Override
public void sleep(long milliseconds) throws InterruptedException {
Thread.sleep(milliseconds);
}
}

View File

@@ -14,7 +14,7 @@ dependencies {
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.4.1'
implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.3'
apt 'com.google.dagger:dagger-compiler:2.0.2'

View File

@@ -10,6 +10,9 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeyManager;
@@ -18,21 +21,32 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
@ThreadSafe
@NotNullByDefault
class ContactManagerImpl implements ContactManager {
private final DatabaseComponent db;
private final KeyManager keyManager;
private final IdentityManager identityManager;
private final List<ContactHook> hooks;
@Inject
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager,
IdentityManager identityManager) {
this.db = db;
this.keyManager = keyManager;
this.identityManager = identityManager;
hooks = new CopyOnWriteArrayList<>();
}
@@ -148,6 +162,17 @@ class ContactManagerImpl implements ContactManager {
db.setContactActive(txn, c, active);
}
@Override
public void setContactAlias(ContactId c, @Nullable String alias)
throws DbException {
if (alias != null) {
int aliasLength = toUtf8(alias).length;
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException();
}
db.transaction(false, txn -> db.setContactAlias(txn, c, alias));
}
@Override
public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException {
@@ -176,4 +201,23 @@ class ContactManagerImpl implements ContactManager {
db.removeContact(txn, c);
}
@Override
public AuthorInfo getAuthorInfo(AuthorId a) throws DbException {
return db.transactionWithResult(true, txn -> getAuthorInfo(txn, a));
}
@Override
public AuthorInfo getAuthorInfo(Transaction txn, AuthorId authorId)
throws DbException {
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.getId().equals(authorId))
return new AuthorInfo(OURSELVES);
Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
if (contacts.isEmpty()) return new AuthorInfo(UNKNOWN);
if (contacts.size() > 1) throw new AssertionError();
Contact c = contacts.iterator().next();
if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias());
else return new AuthorInfo(UNVERIFIED, c.getAlias());
}
}

View File

@@ -616,6 +616,12 @@ interface Database<T> {
void setContactActive(T txn, ContactId c, boolean active)
throws DbException;
/**
* Sets an alias name for a contact.
*/
void setContactAlias(T txn, ContactId c, @Nullable String alias)
throws DbException;
/**
* Sets the given group's visibility to the given contact to either
* {@link Visibility VISIBLE} or {@link Visibility SHARED}.

View File

@@ -9,7 +9,9 @@ import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbCallable;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.DbRunnable;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.NoSuchContactException;
@@ -166,6 +168,31 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
for (Event e : transaction.getEvents()) eventBus.broadcast(e);
}
@Override
public <E extends Exception> void transaction(boolean readOnly,
DbRunnable<E> task) throws DbException, E {
Transaction txn = startTransaction(readOnly);
try {
task.run(txn);
commitTransaction(txn);
} finally {
endTransaction(txn);
}
}
@Override
public <R, E extends Exception> R transactionWithResult(boolean readOnly,
DbCallable<R, E> task) throws DbException, E {
Transaction txn = startTransaction(readOnly);
try {
R result = task.call(txn);
commitTransaction(txn);
return result;
} finally {
endTransaction(txn);
}
}
private T unbox(Transaction transaction) {
if (transaction.isCommitted()) throw new IllegalStateException();
return txnClass.cast(transaction.unbox());
@@ -832,6 +859,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new ContactStatusChangedEvent(c, active));
}
@Override
public void setContactAlias(Transaction transaction, ContactId c,
String alias) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
db.setContactAlias(txn, c, alias);
}
@Override
public void setGroupVisibility(Transaction transaction, ContactId c,
GroupId g, Visibility v) throws DbException {

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.settings.Settings;
import static java.util.concurrent.TimeUnit.DAYS;
interface DatabaseConstants {
/**
@@ -23,4 +25,16 @@ interface DatabaseConstants {
*/
String SCHEMA_VERSION_KEY = "schemaVersion";
/**
* The {@link Settings} key under which the time of the last database
* compaction is stored.
*/
String LAST_COMPACTED_KEY = "lastCompacted";
/**
* The maximum time between database compactions in milliseconds. When the
* database is opened it will be compacted if more than this amount of time
* has passed since the last compaction.
*/
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.db;
class DatabaseTypes {
private final String hashType, secretType, binaryType;
private final String counterType, stringType;
public DatabaseTypes(String hashType, String secretType, String binaryType,
String counterType, String stringType) {
this.hashType = hashType;
this.secretType = secretType;
this.binaryType = binaryType;
this.counterType = counterType;
this.stringType = stringType;
}
/**
* Replaces database type placeholders in a statement with the actual types.
* These placeholders are currently supported:
* <li> _HASH
* <li> _SECRET
* <li> _BINARY
* <li> _COUNTER
* <li> _STRING
*/
String replaceTypes(String s) {
s = s.replaceAll("_HASH", hashType);
s = s.replaceAll("_SECRET", secretType);
s = s.replaceAll("_BINARY", binaryType);
s = s.replaceAll("_COUNTER", counterType);
s = s.replaceAll("_STRING", stringType);
return s;
}
}

View File

@@ -13,6 +13,7 @@ import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.annotation.Nullable;
@@ -29,6 +30,8 @@ class H2Database extends JdbcDatabase {
private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
private static final String STRING_TYPE = "VARCHAR";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE);
private final DatabaseConfig config;
private final String url;
@@ -39,8 +42,7 @@ class H2Database extends JdbcDatabase {
@Inject
H2Database(DatabaseConfig config, MessageFactory messageFactory,
Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
messageFactory, clock);
super(dbTypes, messageFactory, clock);
this.config = config;
File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath();
@@ -106,4 +108,22 @@ class H2Database extends JdbcDatabase {
String getUrl() {
return url;
}
@Override
protected void compactAndClose() throws DbException {
Connection c = null;
Statement s = null;
try {
c = createConnection();
closeAllConnections();
s = c.createStatement();
s.execute("SHUTDOWN COMPACT");
s.close();
c.close();
} catch (SQLException e) {
tryToClose(s);
tryToClose(c);
throw new DbException(e);
}
}
}

View File

@@ -30,6 +30,8 @@ class HyperSqlDatabase extends JdbcDatabase {
private static final String COUNTER_TYPE =
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
private static final String STRING_TYPE = "VARCHAR";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE);
private final DatabaseConfig config;
private final String url;
@@ -40,8 +42,7 @@ class HyperSqlDatabase extends JdbcDatabase {
@Inject
HyperSqlDatabase(DatabaseConfig config, MessageFactory messageFactory,
Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
messageFactory, clock);
super(dbTypes, messageFactory, clock);
this.config = config;
File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath();
@@ -61,20 +62,24 @@ class HyperSqlDatabase extends JdbcDatabase {
@Override
public void close() throws DbException {
Connection c = null;
Statement s = null;
try {
super.closeAllConnections();
Connection c = createConnection();
Statement s = c.createStatement();
c = createConnection();
s = c.createStatement();
s.executeQuery("SHUTDOWN");
s.close();
c.close();
} catch (SQLException e) {
tryToClose(s);
tryToClose(c);
throw new DbException(e);
}
}
@Override
public long getFreeSpace() throws DbException {
public long getFreeSpace() {
File dir = config.getDatabaseDirectory();
long maxSize = config.getMaxSize();
long free = dir.getFreeSpace();
@@ -104,4 +109,22 @@ class HyperSqlDatabase extends JdbcDatabase {
String hex = StringUtils.toHexString(key.getBytes());
return DriverManager.getConnection(url + ";crypt_key=" + hex);
}
@Override
protected void compactAndClose() throws DbException {
Connection c = null;
Statement s = null;
try {
super.closeAllConnections();
c = createConnection();
s = c.createStatement();
s.executeQuery("SHUTDOWN COMPACT");
s.close();
c.close();
} catch (SQLException e) {
tryToClose(s);
tryToClose(c);
throw new DbException(e);
}
}
}

View File

@@ -56,6 +56,7 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.sql.Types.INTEGER;
import static java.sql.Types.VARCHAR;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
@@ -67,9 +68,13 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
/**
* A generic database implementation that can be used with any JDBC-compatible
@@ -79,7 +84,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing
static final int CODE_SCHEMA_VERSION = 40;
static final int CODE_SCHEMA_VERSION = 41;
// Rotation period offsets for incoming transport keys
private static final int OFFSET_PREV = -1;
@@ -109,6 +114,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " authorId _HASH NOT NULL,"
+ " formatVersion INT NOT NULL,"
+ " name _STRING NOT NULL,"
+ " alias _STRING," // Null if no alias exists
+ " publicKey _BINARY NOT NULL,"
+ " localAuthorId _HASH NOT NULL,"
+ " verified BOOLEAN NOT NULL,"
@@ -306,10 +312,9 @@ abstract class JdbcDatabase implements Database<Connection> {
Logger.getLogger(JdbcDatabase.class.getName());
// Different database libraries use different names for certain types
private final String hashType, secretType, binaryType;
private final String counterType, stringType;
private final MessageFactory messageFactory;
private final Clock clock;
private final DatabaseTypes dbTypes;
// Locking: connectionsLock
private final LinkedList<Connection> connections = new LinkedList<>();
@@ -317,20 +322,16 @@ abstract class JdbcDatabase implements Database<Connection> {
private int openConnections = 0; // Locking: connectionsLock
private boolean closed = false; // Locking: connectionsLock
@Nullable
protected abstract Connection createConnection() throws SQLException;
protected abstract void compactAndClose() throws DbException;
private final Lock connectionsLock = new ReentrantLock();
private final Condition connectionsChanged = connectionsLock.newCondition();
JdbcDatabase(String hashType, String secretType, String binaryType,
String counterType, String stringType,
MessageFactory messageFactory, Clock clock) {
this.hashType = hashType;
this.secretType = secretType;
this.binaryType = binaryType;
this.counterType = counterType;
this.stringType = stringType;
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
Clock clock) {
this.dbTypes = databaseTypes;
this.messageFactory = messageFactory;
this.clock = clock;
}
@@ -344,13 +345,16 @@ abstract class JdbcDatabase implements Database<Connection> {
throw new DbException(e);
}
// Open the database and create the tables and indexes if necessary
boolean compact;
Connection txn = startTransaction();
try {
if (reopen) {
checkSchemaVersion(txn, listener);
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
} else {
createTables(txn);
storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
initialiseSettings(txn);
compact = false;
}
createIndexes(txn);
commitTransaction(txn);
@@ -358,6 +362,25 @@ abstract class JdbcDatabase implements Database<Connection> {
abortTransaction(txn);
throw e;
}
// Compact the database if necessary
if (compact) {
if (listener != null) listener.onDatabaseCompaction();
long start = now();
compactAndClose();
logDuration(LOG, "Compacting database", start);
// Allow the next transaction to reopen the DB
synchronized (connectionsLock) {
closed = false;
}
txn = startTransaction();
try {
storeLastCompacted(txn);
commitTransaction(txn);
} catch (DbException e) {
abortTransaction(txn);
throw e;
}
}
}
/**
@@ -365,17 +388,18 @@ abstract class JdbcDatabase implements Database<Connection> {
* version used by the current code and applies any suitable migrations to
* the data if necessary.
*
* @return true if any migrations were applied, false if the schema was
* already current
* @throws DataTooNewException if the data uses a newer schema than the
* current code
* @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated
*/
private void checkSchemaVersion(Connection txn,
private boolean migrateSchema(Connection txn, Settings s,
@Nullable MigrationListener listener) throws DbException {
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
if (dataSchemaVersion == -1) throw new DbException();
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return;
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return false;
if (CODE_SCHEMA_VERSION < dataSchemaVersion)
throw new DataTooNewException();
// Apply any suitable migrations in order
@@ -384,7 +408,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (start == dataSchemaVersion) {
if (LOG.isLoggable(INFO))
LOG.info("Migrating from schema " + start + " to " + end);
if (listener != null) listener.onMigrationRun();
if (listener != null) listener.onDatabaseMigration();
// Apply the migration
m.migrate(txn);
// Store the new schema version
@@ -394,11 +418,24 @@ abstract class JdbcDatabase implements Database<Connection> {
}
if (dataSchemaVersion != CODE_SCHEMA_VERSION)
throw new DataTooOldException();
return true;
}
// Package access for testing
List<Migration<Connection>> getMigrations() {
return Arrays.asList(new Migration38_39(), new Migration39_40());
return Arrays.asList(
new Migration38_39(),
new Migration39_40(),
new Migration40_41(dbTypes)
);
}
private boolean isCompactionDue(Settings s) {
long lastCompacted = s.getLong(LAST_COMPACTED_KEY, 0);
long elapsed = clock.currentTimeMillis() - lastCompacted;
if (LOG.isLoggable(INFO))
LOG.info(elapsed + " ms since last compaction");
return elapsed > MAX_COMPACTION_INTERVAL_MS;
}
private void storeSchemaVersion(Connection txn, int version)
@@ -408,6 +445,19 @@ abstract class JdbcDatabase implements Database<Connection> {
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private void storeLastCompacted(Connection txn) throws DbException {
Settings s = new Settings();
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private void initialiseSettings(Connection txn) throws DbException {
Settings s = new Settings();
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private void tryToClose(@Nullable ResultSet rs) {
try {
if (rs != null) rs.close();
@@ -416,7 +466,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
private void tryToClose(@Nullable Statement s) {
protected void tryToClose(@Nullable Statement s) {
try {
if (s != null) s.close();
} catch (SQLException e) {
@@ -424,24 +474,32 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
protected void tryToClose(@Nullable Connection c) {
try {
if (c != null) c.close();
} catch (SQLException e) {
logException(LOG, WARNING, e);
}
}
private void createTables(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.executeUpdate(insertTypeNames(CREATE_SETTINGS));
s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
s.executeUpdate(insertTypeNames(CREATE_GROUPS));
s.executeUpdate(insertTypeNames(CREATE_GROUP_METADATA));
s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
s.executeUpdate(insertTypeNames(CREATE_MESSAGE_METADATA));
s.executeUpdate(insertTypeNames(CREATE_MESSAGE_DEPENDENCIES));
s.executeUpdate(insertTypeNames(CREATE_OFFERS));
s.executeUpdate(insertTypeNames(CREATE_STATUSES));
s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS));
s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_SETTINGS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_LOCAL_AUTHORS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_CONTACTS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_GROUPS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_GROUP_METADATA));
s.executeUpdate(dbTypes.replaceTypes(CREATE_GROUP_VISIBILITIES));
s.executeUpdate(dbTypes.replaceTypes(CREATE_MESSAGES));
s.executeUpdate(dbTypes.replaceTypes(CREATE_MESSAGE_METADATA));
s.executeUpdate(dbTypes.replaceTypes(CREATE_MESSAGE_DEPENDENCIES));
s.executeUpdate(dbTypes.replaceTypes(CREATE_OFFERS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_STATUSES));
s.executeUpdate(dbTypes.replaceTypes(CREATE_TRANSPORTS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_OUTGOING_KEYS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS));
s.close();
} catch (SQLException e) {
tryToClose(s);
@@ -466,15 +524,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
private String insertTypeNames(String s) {
s = s.replaceAll("_HASH", hashType);
s = s.replaceAll("_SECRET", secretType);
s = s.replaceAll("_BINARY", binaryType);
s = s.replaceAll("_COUNTER", counterType);
s = s.replaceAll("_STRING", stringType);
return s;
}
@Override
public Connection startTransaction() throws DbException {
Connection txn;
@@ -489,7 +538,6 @@ abstract class JdbcDatabase implements Database<Connection> {
if (txn == null) {
// Open a new connection
txn = createConnection();
if (txn == null) throw new DbException();
txn.setAutoCommit(false);
connectionsLock.lock();
try {
@@ -1201,8 +1249,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT authorId, formatVersion, name, publicKey,"
+ " localAuthorId, verified, active"
String sql = "SELECT authorId, formatVersion, name, alias,"
+ " publicKey, localAuthorId, verified, active"
+ " FROM contacts"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
@@ -1212,15 +1260,17 @@ abstract class JdbcDatabase implements Database<Connection> {
AuthorId authorId = new AuthorId(rs.getBytes(1));
int formatVersion = rs.getInt(2);
String name = rs.getString(3);
byte[] publicKey = rs.getBytes(4);
AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
boolean verified = rs.getBoolean(6);
boolean active = rs.getBoolean(7);
String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5);
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
boolean verified = rs.getBoolean(7);
boolean active = rs.getBoolean(8);
rs.close();
ps.close();
Author author =
new Author(authorId, formatVersion, name, publicKey);
return new Contact(c, author, localAuthorId, verified, active);
return new Contact(c, author, localAuthorId, alias, verified,
active);
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
@@ -1235,7 +1285,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null;
try {
String sql = "SELECT contactId, authorId, formatVersion, name,"
+ " publicKey, localAuthorId, verified, active"
+ " alias, publicKey, localAuthorId, verified, active"
+ " FROM contacts";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
@@ -1245,14 +1295,15 @@ abstract class JdbcDatabase implements Database<Connection> {
AuthorId authorId = new AuthorId(rs.getBytes(2));
int formatVersion = rs.getInt(3);
String name = rs.getString(4);
byte[] publicKey = rs.getBytes(5);
String alias = rs.getString(5);
byte[] publicKey = rs.getBytes(6);
Author author =
new Author(authorId, formatVersion, name, publicKey);
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
boolean verified = rs.getBoolean(7);
boolean active = rs.getBoolean(8);
AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
boolean verified = rs.getBoolean(8);
boolean active = rs.getBoolean(9);
contacts.add(new Contact(contactId, author, localAuthorId,
verified, active));
alias, verified, active));
}
rs.close();
ps.close();
@@ -1293,8 +1344,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT contactId, formatVersion, name, publicKey,"
+ " localAuthorId, verified, active"
String sql = "SELECT contactId, formatVersion, name, alias,"
+ " publicKey, localAuthorId, verified, active"
+ " FROM contacts"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
@@ -1305,14 +1356,15 @@ abstract class JdbcDatabase implements Database<Connection> {
ContactId c = new ContactId(rs.getInt(1));
int formatVersion = rs.getInt(2);
String name = rs.getString(3);
byte[] publicKey = rs.getBytes(4);
AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
boolean verified = rs.getBoolean(6);
boolean active = rs.getBoolean(7);
String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5);
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
boolean verified = rs.getBoolean(7);
boolean active = rs.getBoolean(8);
Author author =
new Author(remote, formatVersion, name, publicKey);
contacts.add(new Contact(c, author, localAuthorId, verified,
active));
contacts.add(new Contact(c, author, localAuthorId, alias,
verified, active));
}
rs.close();
ps.close();
@@ -1508,7 +1560,7 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close();
ps.close();
if (raw == null) throw new MessageDeletedException();
if (raw.length < MESSAGE_HEADER_LENGTH) throw new AssertionError();
if (raw.length <= MESSAGE_HEADER_LENGTH) throw new AssertionError();
byte[] body = new byte[raw.length - MESSAGE_HEADER_LENGTH];
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
return new Message(m, g, timestamp, body);
@@ -2737,6 +2789,25 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void setContactAlias(Connection txn, ContactId c,
@Nullable String alias) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE contacts SET alias = ? WHERE contactId = ?";
ps = txn.prepareStatement(sql);
if (alias == null) ps.setNull(1, VARCHAR);
else ps.setString(1, alias);
ps.setInt(2, c.getInt());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
@Override
public void setGroupVisibility(Connection txn, ContactId c, GroupId g,
boolean shared) throws DbException {

View File

@@ -0,0 +1,56 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
class Migration40_41 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration40_41.class.getName());
private final DatabaseTypes dbTypes;
public Migration40_41(DatabaseTypes databaseTypes) {
this.dbTypes = databaseTypes;
}
@Override
public int getStartVersion() {
return 40;
}
@Override
public int getEndVersion() {
return 41;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.execute("ALTER TABLE contacts"
+ dbTypes.replaceTypes(" ADD alias VARCHAR"));
} catch (SQLException e) {
tryToClose(s);
throw new DbException(e);
}
}
private void tryToClose(@Nullable Statement s) {
try {
if (s != null) s.close();
} catch (SQLException e) {
logException(LOG, WARNING, e);
}
}
}

View File

@@ -1,29 +1,21 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author.Status;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -118,26 +110,4 @@ class IdentityManagerImpl implements IdentityManager {
return db.getLocalAuthors(txn).iterator().next();
}
@Override
public Status getAuthorStatus(AuthorId authorId) throws DbException {
Transaction txn = db.startTransaction(true);
try {
return getAuthorStatus(txn, authorId);
} finally {
db.endTransaction(txn);
}
}
@Override
public Status getAuthorStatus(Transaction txn, AuthorId authorId)
throws DbException {
if (getLocalAuthor(txn).getId().equals(authorId)) return OURSELVES;
Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
if (contacts.isEmpty()) return UNKNOWN;
for (Contact c : contacts) {
if (c.isVerified()) return VERIFIED;
}
return UNVERIFIED;
}
}

View File

@@ -29,6 +29,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
@@ -159,11 +160,17 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
}
@Override
public void onMigrationRun() {
public void onDatabaseMigration() {
state = MIGRATING_DATABASE;
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
}
@Override
public void onDatabaseCompaction() {
state = COMPACTING_DATABASE;
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
}
@Override
public void stopServices() {
try {

View File

@@ -23,7 +23,6 @@ import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.util.StringUtils;
import java.io.IOException;
import java.security.SecureRandom;
@@ -46,6 +45,9 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.macToBytes;
import static org.briarproject.bramble.util.StringUtils.macToString;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -96,6 +98,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
abstract DuplexTransportConnection connectTo(String address, String uuid)
throws IOException;
@Nullable
abstract DuplexTransportConnection discoverAndConnect(String uuid);
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
@@ -193,7 +198,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
address = getBluetoothAddress();
if (LOG.isLoggable(INFO))
LOG.info("Local address " + scrubMacAddress(address));
if (!StringUtils.isNullOrEmpty(address)) {
if (!isNullOrEmpty(address)) {
p.put(PROP_ADDRESS, address);
changed = true;
}
@@ -256,9 +261,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
// Try to connect to known devices in parallel
for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
if (isNullOrEmpty(address)) continue;
String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
if (isNullOrEmpty(uuid)) continue;
ContactId c = e.getKey();
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return;
@@ -309,9 +314,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (!isRunning() || !shouldAllowContactConnections()) return null;
if (!connectionLimiter.canOpenContactConnection()) return null;
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
if (isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
if (isNullOrEmpty(uuid)) return null;
DuplexTransportConnection conn = connect(address, uuid);
if (conn == null) return null;
// TODO: Why don't we reset the backoff here?
@@ -326,9 +331,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) return null;
// There's no point listening if we can't discover our own address
String address = getBluetoothAddress();
if (address == null) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
@@ -346,7 +348,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
}
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH);
descriptor.add(StringUtils.macToBytes(address));
String address = getBluetoothAddress();
if (address != null) descriptor.add(macToBytes(address));
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@@ -354,18 +357,25 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
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);
DuplexTransportConnection conn = connect(address, uuid);
DuplexTransportConnection conn;
if (descriptor.size() == 1) {
if (LOG.isLoggable(INFO))
LOG.info("Discovering address for key agreement UUID " + uuid);
conn = discoverAndConnect(uuid);
} else {
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
conn = connect(address, uuid);
}
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
return conn;
}
@@ -373,7 +383,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return StringUtils.macToString(mac);
return macToString(mac);
}
@Override

View File

@@ -28,7 +28,6 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.Closeable;
import java.io.EOFException;
@@ -70,9 +69,11 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -87,7 +88,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000;
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION = Pattern.compile("[a-z2-7]{16}");
private static final Pattern ONION_V2 = Pattern.compile("[a-z2-7]{16}");
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
private final Executor ioExecutor, connectionStatusExecutor;
private final NetworkManager networkManager;
@@ -362,7 +364,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// If there's already a port number stored in config, reuse it
String portString = settings.get(PREF_TOR_PORT);
int port;
if (StringUtils.isNullOrEmpty(portString)) port = 0;
if (isNullOrEmpty(portString)) port = 0;
else port = Integer.parseInt(portString);
// Bind a server socket to receive connections from Tor
ServerSocket ss = null;
@@ -427,11 +429,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return;
}
// Publish the hidden service's onion hostname in transport properties
String hostname = response.get(HS_ADDRESS);
String onion2 = response.get(HS_ADDRESS);
if (LOG.isLoggable(INFO))
LOG.info("Hidden service " + scrubOnion(hostname));
LOG.info("Hidden service " + scrubOnion(onion2));
TransportProperties p = new TransportProperties();
p.put(PROP_ONION, hostname);
p.put(PROP_ONION_V2, onion2);
callback.mergeLocalProperties(p);
if (privKey == null) {
// Save the hidden service's private key for next time
@@ -530,26 +532,41 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null;
String onion = p.get(PROP_ONION);
if (StringUtils.isNullOrEmpty(onion)) return null;
if (!ONION.matcher(onion).matches()) {
// not scrubbing this address, so we are able to find the problem
if (LOG.isLoggable(INFO)) LOG.info("Invalid hostname: " + onion);
return null;
String bestOnion = null;
String onion2 = p.get(PROP_ONION_V2);
String onion3 = p.get(PROP_ONION_V3);
if (!isNullOrEmpty(onion2)) {
if (ONION_V2.matcher(onion2).matches()) {
bestOnion = onion2;
} else {
// Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO))
LOG.info("Invalid v2 hostname: " + onion2);
}
}
if (!isNullOrEmpty(onion3)) {
if (ONION_V3.matcher(onion3).matches()) {
bestOnion = onion3;
} else {
// Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO))
LOG.info("Invalid v3 hostname: " + onion3);
}
}
if (bestOnion == null) return null;
Socket s = null;
try {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubOnion(onion));
s = torSocketFactory.createSocket(onion + ".onion", 80);
LOG.info("Connecting to " + scrubOnion(bestOnion));
s = torSocketFactory.createSocket(bestOnion + ".onion", 80);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubOnion(onion));
LOG.info("Connected to " + scrubOnion(bestOnion));
return new TorTransportConnection(this, s);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to " + scrubOnion(onion) + ": " +
e.toString());
LOG.info("Could not connect to " + scrubOnion(bestOnion)
+ ": " + e.toString());
}
tryToClose(s);
return null;
@@ -627,6 +644,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated");
settings = s.getSettings();
// Works around a bug introduced in Tor 0.3.4.8. Could be
// replaced with callback.transportDisabled() when fixed.
disableNetwork();
updateConnectionStatus(networkManager.getNetworkStatus());
}
} else if (e instanceof NetworkStatusEvent) {
@@ -634,6 +654,16 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void disableNetwork() {
connectionStatusExecutor.execute(() -> {
try {
enableNetwork(false);
} catch (IOException ex) {
logException(LOG, WARNING, ex);
}
});
}
private void updateConnectionStatus(NetworkStatus status) {
connectionStatusExecutor.execute(() -> {
if (!running) return;

View File

@@ -36,6 +36,7 @@ class MessageFactoryImpl implements MessageFactory {
@Override
public Message createMessage(GroupId g, long timestamp, byte[] body) {
if (body.length == 0) throw new IllegalArgumentException();
if (body.length > MAX_MESSAGE_BODY_LENGTH)
throw new IllegalArgumentException();
MessageId id = getMessageId(g, timestamp, body);
@@ -54,7 +55,7 @@ class MessageFactoryImpl implements MessageFactory {
@Override
public Message createMessage(byte[] raw) {
if (raw.length < MESSAGE_HEADER_LENGTH)
if (raw.length <= MESSAGE_HEADER_LENGTH)
throw new IllegalArgumentException();
if (raw.length > MAX_MESSAGE_LENGTH)
throw new IllegalArgumentException();

View File

@@ -124,7 +124,8 @@ class SyncRecordReaderImpl implements SyncRecordReader {
if (!hasMessage()) throw new FormatException();
if (nextRecord == null) throw new AssertionError();
byte[] payload = nextRecord.getPayload();
if (payload.length < MESSAGE_HEADER_LENGTH) throw new FormatException();
if (payload.length <= MESSAGE_HEADER_LENGTH)
throw new FormatException();
// Validate timestamp
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
if (timestamp < 0) throw new FormatException();

View File

@@ -79,18 +79,6 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
assertSame(meta, messageContext.getMetadata());
}
@Test(expected = InvalidMessageException.class)
public void testRejectsTooShortMessage() throws Exception {
Message invalidMessage = getMessage(groupId, 0);
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
}});
failIfSubclassIsCalled.validateMessage(invalidMessage, group);
}
@Test
public void testAcceptsMinLengthMessage() throws Exception {
Message shortMessage = getMessage(groupId, 1);

View File

@@ -5,12 +5,17 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
@@ -20,10 +25,20 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Random;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class ContactManagerImplTest extends BrambleMockTestCase {
@@ -31,16 +46,20 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
private final Mockery context = new Mockery();
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final KeyManager keyManager = context.mock(KeyManager.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final ContactManager contactManager;
private final ContactId contactId = new ContactId(42);
private final Author remote = getAuthor();
private final AuthorId local = new AuthorId(getRandomId());
private final LocalAuthor localAuthor = getLocalAuthor();
private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final boolean verified = false, active = true;
private final Contact contact =
new Contact(contactId, remote, local, verified, active);
new Contact(contactId, remote, local, alias, verified, active);
public ContactManagerImplTest() {
contactManager = new ContactManagerImpl(db, keyManager);
contactManager = new ContactManagerImpl(db, keyManager, identityManager);
}
@Test
@@ -105,7 +124,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(Collections.emptyList()));
will(returnValue(emptyList()));
oneOf(db).endTransaction(txn);
}});
@@ -131,7 +150,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
public void testActiveContacts() throws Exception {
Collection<Contact> activeContacts = Collections.singletonList(contact);
Collection<Contact> contacts = new ArrayList<>(activeContacts);
contacts.add(new Contact(new ContactId(3), remote, local, true, false));
contacts.add(new Contact(new ContactId(3), remote, local, alias, true,
false));
Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
@@ -171,6 +191,23 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
contactManager.setContactActive(txn, contactId, active);
}
@Test
public void testSetContactAlias() throws Exception {
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(equal(false)), withDbRunnable(txn));
oneOf(db).setContactAlias(txn, contactId, alias);
}});
contactManager.setContactAlias(contactId, alias);
}
@Test(expected = IllegalArgumentException.class)
public void testSetContactAliasTooLong() throws Exception {
contactManager.setContactAlias(contactId,
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1));
}
@Test
public void testContactExists() throws Exception {
Transaction txn = new Transaction(null, true);
@@ -186,4 +223,79 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
assertTrue(contactManager.contactExists(remote.getId(), local));
}
@Test
public void testGetAuthorStatus() throws Exception {
Transaction txn = new Transaction(null, true);
Collection<Contact> contacts = singletonList(
new Contact(new ContactId(1), remote, localAuthor.getId(),
alias, false, true));
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(equal(true)),
withDbCallable(txn));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(contacts));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(alias, contact.getAlias());
}
@Test
public void testGetAuthorStatusTransaction() throws DbException {
Transaction txn = new Transaction(null, true);
// check unknown author
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(emptyList()));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNKNOWN, authorInfo.getStatus());
assertNull(authorInfo.getAlias());
// check unverified contact
Collection<Contact> contacts = singletonList(
new Contact(new ContactId(1), remote, localAuthor.getId(),
alias, false, true));
checkAuthorStatusContext(txn, remote.getId(), contacts);
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(alias, contact.getAlias());
// check verified contact
contacts = singletonList(new Contact(new ContactId(1), remote,
localAuthor.getId(), alias, true, true));
checkAuthorStatusContext(txn, remote.getId(), contacts);
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(VERIFIED, authorInfo.getStatus());
assertEquals(alias, contact.getAlias());
// check ourselves
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
never(db).getContactsByAuthorId(txn, remote.getId());
}});
authorInfo = contactManager.getAuthorInfo(txn, localAuthor.getId());
assertEquals(OURSELVES, authorInfo.getStatus());
assertNull(authorInfo.getAlias());
}
private void checkAuthorStatusContext(Transaction txn, AuthorId authorId,
Collection<Contact> contacts) throws DbException {
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(contacts));
}});
}
}

View File

@@ -77,6 +77,7 @@ import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -99,6 +100,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final Group group;
private final Author author;
private final LocalAuthor localAuthor;
private final String alias;
private final Message message, message1;
private final MessageId messageId, messageId1;
private final Metadata metadata;
@@ -115,6 +117,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
groupId = group.getId();
author = getAuthor();
localAuthor = getLocalAuthor();
alias = getRandomString(5);
message = getMessage(groupId);
message1 = getMessage(groupId);
messageId = message.getId();
@@ -124,7 +127,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transportId = getTransportId();
maxLatency = Integer.MAX_VALUE;
contactId = new ContactId(234);
contact = new Contact(contactId, author, localAuthor.getId(),
contact = new Contact(contactId, author, localAuthor.getId(), alias,
true, true);
keySetId = new KeySetId(345);
}
@@ -288,11 +291,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception {
context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not)
exactly(16).of(database).startTransaction();
exactly(17).of(database).startTransaction();
will(returnValue(txn));
exactly(16).of(database).containsContact(txn, contactId);
exactly(17).of(database).containsContact(txn, contactId);
will(returnValue(false));
exactly(16).of(database).abortTransaction(txn);
exactly(17).of(database).abortTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
@@ -450,6 +453,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.endTransaction(transaction);
}
transaction = db.startTransaction(false);
try {
db.setContactAlias(transaction, contactId, alias);
fail();
} catch (NoSuchContactException expected) {
// Expected
} finally {
db.endTransaction(transaction);
}
transaction = db.startTransaction(false);
try {
db.setGroupVisibility(transaction, contactId, groupId, SHARED);

View File

@@ -26,11 +26,10 @@ import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.ArrayClock;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.SettableClock;
import org.briarproject.bramble.test.TestDatabaseConfig;
import org.briarproject.bramble.test.TestMessageFactory;
import org.briarproject.bramble.test.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -46,6 +45,7 @@ import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
@@ -53,6 +53,7 @@ import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -61,6 +62,9 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getClientId;
@@ -71,6 +75,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -1710,6 +1715,39 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close();
}
@Test
public void testSetContactAlias() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact
db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true));
// The contact should have no alias
Contact contact = db.getContact(txn, contactId);
assertNull(contact.getAlias());
// Set a contact alias
String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
db.setContactAlias(txn, contactId, alias);
// The contact should have an alias
contact = db.getContact(txn, contactId);
assertEquals(alias, contact.getAlias());
// Set the contact alias null
db.setContactAlias(txn, contactId, null);
// The contact should have no alias
contact = db.getContact(txn, contactId);
assertNull(contact.getAlias());
db.commitTransaction(txn);
db.close();
}
@Test
public void testSetMessageState() throws Exception {
Database<Connection> db = open(false);
@@ -1818,10 +1856,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test
public void testMessageRetransmission() throws Exception {
long now = System.currentTimeMillis();
long steps[] = {now, now, now + MAX_LATENCY * 2 - 1,
now + MAX_LATENCY * 2};
AtomicLong time = new AtomicLong(now);
Database<Connection> db =
open(false, new TestMessageFactory(), new ArrayClock(steps));
open(false, new TestMessageFactory(), new SettableClock(time));
Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message
@@ -1847,11 +1884,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable
time.set(now + MAX_LATENCY * 2 - 1);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Time: now + MAX_LATENCY * 2
// The message should have expired and should now be sendable
time.set(now + MAX_LATENCY * 2);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
@@ -1859,13 +1898,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close();
}
@Test
public void testFasterMessageRetransmission() throws Exception {
long now = System.currentTimeMillis();
long steps[] = {now, now, now, now, now + 1};
AtomicLong time = new AtomicLong(now);
Database<Connection> db =
open(false, new TestMessageFactory(), new ArrayClock(steps));
open(false, new TestMessageFactory(), new SettableClock(time));
Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message
@@ -1903,6 +1941,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Time: now + 1
// The message should no longer be sendable via the faster transport,
// as the ETA is now equal
time.set(now + 1);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
MAX_LATENCY - 1);
assertTrue(ids.isEmpty());
@@ -1911,6 +1950,45 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close();
}
@Test
public void testCompactionTime() throws Exception {
MessageFactory messageFactory = new TestMessageFactory();
long now = System.currentTimeMillis();
AtomicLong time = new AtomicLong(now);
Clock clock = new SettableClock(time);
// Time: now
// The last compaction time should be initialised to the current time
Database<Connection> db = open(false, messageFactory, clock);
Connection txn = db.startTransaction();
Settings s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
assertEquals(now, s.getLong(LAST_COMPACTED_KEY, 0));
db.commitTransaction(txn);
db.close();
// Time: now + MAX_COMPACTION_INTERVAL_MS
// The DB should not be compacted, so the last compaction time should
// not be updated
time.set(now + MAX_COMPACTION_INTERVAL_MS);
db = open(true, messageFactory, clock);
txn = db.startTransaction();
s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
assertEquals(now, s.getLong(LAST_COMPACTED_KEY, 0));
db.commitTransaction(txn);
db.close();
// Time: now + MAX_COMPACTION_INTERVAL_MS + 1
// The DB should be compacted, so the last compaction time should be
// updated
time.set(now + MAX_COMPACTION_INTERVAL_MS + 1);
db = open(true, messageFactory, clock);
txn = db.startTransaction();
s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
assertEquals(now + MAX_COMPACTION_INTERVAL_MS + 1,
s.getLong(LAST_COMPACTED_KEY, 0));
db.commitTransaction(txn);
db.close();
}
private Database<Connection> open(boolean resume) throws Exception {
return open(resume, new TestMessageFactory(), new SystemClock());
@@ -1921,7 +1999,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Database<Connection> db =
createDatabase(new TestDatabaseConfig(testDir, MAX_SIZE),
messageFactory, clock);
if (!resume) TestUtils.deleteTestDirectory(testDir);
if (!resume) deleteTestDirectory(testDir);
db.open(key, null);
return db;
}

View File

@@ -1,7 +1,5 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
@@ -9,9 +7,7 @@ import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.test.BrambleMockTestCase;
@@ -19,15 +15,9 @@ import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.junit.Assert.assertEquals;
@@ -107,60 +97,4 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
assertEquals(localAuthor, identityManager.getLocalAuthor());
}
@Test
public void testGetAuthorStatus() throws DbException {
Author author = getAuthor();
AuthorId authorId = author.getId();
Collection<Contact> contacts = new ArrayList<>();
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getLocalAuthors(txn);
will(returnValue(localAuthors));
oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(contacts));
oneOf(db).endTransaction(txn);
}});
assertEquals(UNKNOWN, identityManager.getAuthorStatus(authorId));
// add one unverified contact
Contact contact = new Contact(new ContactId(1), author,
localAuthor.getId(), false, true);
contacts.add(contact);
checkAuthorStatusContext(authorId, contacts);
assertEquals(UNVERIFIED, identityManager.getAuthorStatus(authorId));
// add one verified contact
Contact contact2 = new Contact(new ContactId(1), author,
localAuthor.getId(), true, true);
contacts.add(contact2);
checkAuthorStatusContext(authorId, contacts);
assertEquals(VERIFIED, identityManager.getAuthorStatus(authorId));
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
never(db).getLocalAuthors(txn);
never(db).getContactsByAuthorId(txn, authorId);
oneOf(db).endTransaction(txn);
}});
assertEquals(OURSELVES,
identityManager.getAuthorStatus(localAuthor.getId()));
}
private void checkAuthorStatusContext(AuthorId authorId,
Collection<Contact> contacts) throws DbException {
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
never(db).getLocalAuthors(txn);
oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(contacts));
oneOf(db).endTransaction(txn);
}});
}
}

View File

@@ -39,6 +39,7 @@ import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -612,7 +613,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
private Contact getContact(boolean active) {
ContactId c = new ContactId(nextContactId++);
return new Contact(c, getAuthor(), localAuthor.getId(),
true, active);
getRandomString(5), true, active);
}
private void expectGetLocalProperties(Transaction txn) throws Exception {

View File

@@ -0,0 +1,24 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.db.DbCallable;
import org.briarproject.bramble.api.db.DbRunnable;
import org.briarproject.bramble.api.db.Transaction;
import org.jmock.Expectations;
public class DbExpectations extends Expectations {
protected <E extends Exception> DbRunnable<E> withDbRunnable(
Transaction txn) {
addParameterMatcher(any(DbRunnable.class));
currentBuilder().setAction(new RunTransactionAction(txn));
return null;
}
protected <R, E extends Exception> DbCallable<R, E> withDbCallable(
Transaction txn) {
addParameterMatcher(any(DbCallable.class));
currentBuilder().setAction(new RunTransactionWithResultAction(txn));
return null;
}
}

View File

@@ -0,0 +1,30 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.db.DbRunnable;
import org.briarproject.bramble.api.db.Transaction;
import org.hamcrest.Description;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
public class RunTransactionAction implements Action {
private final Transaction txn;
@SuppressWarnings("WeakerAccess")
public RunTransactionAction(Transaction txn) {
this.txn = txn;
}
@Override
public Object invoke(Invocation invocation) throws Throwable {
DbRunnable task = (DbRunnable) invocation.getParameter(1);
task.run(txn);
return null;
}
@Override
public void describeTo(Description description) {
description.appendText("runs a task inside a database transaction");
}
}

View File

@@ -0,0 +1,27 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.db.DbCallable;
import org.briarproject.bramble.api.db.Transaction;
import org.hamcrest.Description;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
public class RunTransactionWithResultAction implements Action {
private final Transaction txn;
public RunTransactionWithResultAction(Transaction txn) {
this.txn = txn;
}
@Override
public Object invoke(Invocation invocation) throws Throwable {
DbCallable task = (DbCallable) invocation.getParameter(1);
return task.call(txn);
}
@Override
public void describeTo(Description description) {
description.appendText("runs a task inside a database transaction");
}
}

View File

@@ -33,6 +33,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
public class KeyManagerImplTest extends BrambleMockTestCase {
@@ -66,10 +67,10 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
Author remoteAuthor = getAuthor();
AuthorId localAuthorId = new AuthorId(getRandomId());
Collection<Contact> contacts = new ArrayList<>();
contacts.add(new Contact(contactId, remoteAuthor, localAuthorId, true,
true));
contacts.add(new Contact(contactId, remoteAuthor, localAuthorId,
getRandomString(5), true, true));
contacts.add(new Contact(inactiveContactId, remoteAuthor, localAuthorId,
true, false));
getRandomString(5), true, false));
SimplexPluginFactory pluginFactory =
context.mock(SimplexPluginFactory.class);
Collection<SimplexPluginFactory> factories =

View File

@@ -38,6 +38,7 @@ import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@@ -56,7 +57,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Contact contact = new Contact(new ContactId(123),
getAuthor(), getLocalAuthor().getId(), true, true);
getAuthor(), getLocalAuthor().getId(), getRandomString(5), true,
true);
private final ClientId clientId = getClientId();
private final long now = System.currentTimeMillis();
private final Transaction txn = new Transaction(null, false);

View File

@@ -28,6 +28,6 @@ dependencyVerification {
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
'org.whispersystems:curve25519-java:0.5.0:curve25519-java-0.5.0.jar:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d',
]
}

View File

@@ -14,9 +14,9 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'net.java.dev.jna:jna:4.4.0'
implementation 'net.java.dev.jna:jna-platform:4.4.0'
tor 'org.briarproject:tor:0.2.9.16@zip'
implementation 'net.java.dev.jna:jna:4.5.2'
implementation 'net.java.dev.jna:jna-platform:4.5.2'
tor 'org.briarproject:tor:0.3.4.8@zip'
apt 'com.google.dagger:dagger-compiler:2.0.2'

View File

@@ -108,6 +108,12 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
return wrapSocket((StreamConnection) Connector.open(url));
}
@Override
@Nullable
DuplexTransportConnection discoverAndConnect(String uuid) {
return null; // TODO
}
private String makeUrl(String address, String uuid) {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
}

View File

@@ -7,12 +7,12 @@ dependencyVerification {
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.java.dev.jna:jna-platform:4.4.0:jna-platform-4.4.0.jar:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499',
'net.java.dev.jna:jna:4.4.0:jna-4.4.0.jar:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1',
'net.java.dev.jna:jna-platform:4.5.2:jna-platform-4.5.2.jar:f1d00c167d8921c6e23c626ef9f1c3ae0be473c95c68ffa012bc7ae55a87e2d6',
'net.java.dev.jna:jna:4.5.2:jna-4.5.2.jar:0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:tor:0.2.9.16:tor-0.2.9.16.zip:f33091ba414d6a952263981d9059b3d0a9093fd277ae887b3cdd02e8f1936558',
'org.briarproject:tor:0.3.4.8:tor-0.3.4.8.zip:bc0158c34002f471a4fe14a6a481816c918eb520a220bb027f64be902beb757f',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',

View File

@@ -17,13 +17,13 @@ def getStdout = { command, defaultValue ->
android {
compileSdkVersion 28
buildToolsVersion '28.0.2'
buildToolsVersion '28.0.3'
defaultConfig {
minSdkVersion 15
targetSdkVersion 26
versionCode 10102
versionName "1.1.2"
versionCode 10103
versionName "1.1.3"
applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -90,7 +90,7 @@ dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
implementation project(':bramble-android')
def supportVersion = '27.1.1'
def supportVersion = '28.0.0'
implementation "com.android.support:support-v4:$supportVersion"
implementation("com.android.support:appcompat-v7:$supportVersion") {
exclude module: 'support-v4'
@@ -105,16 +105,17 @@ dependencies {
implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.android.support:support-annotations:$supportVersion"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation('ch.acra:acra:4.9.1') {
implementation('ch.acra:acra:4.11') {
exclude module: 'support-v4'
exclude module: 'support-annotations'
}
implementation 'info.guardianproject.panic:panic:0.5'
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.google.zxing:core:3.3.0'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.8.0'
implementation 'com.google.zxing:core:3.3.3'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
implementation 'com.vanniktech:emoji-google:0.5.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'

View File

@@ -19,6 +19,8 @@
-dontnote com.android.org.conscrypt.SSLParametersImpl
-dontnote org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
-dontnote sun.security.ssl.SSLContextImpl
-dontwarn org.conscrypt.OpenSSLProvider
-dontwarn org.conscrypt.Conscrypt
# HTML sanitiser
-keep class org.jsoup.safety.Whitelist

View File

@@ -7,6 +7,7 @@
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
@@ -387,7 +388,7 @@
<activity
android:name="org.briarproject.briar.android.panic.PanicResponderActivity"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
android:theme="@style/Theme.AppCompat.NoActionBar">
<!-- this can never have launchMode singleTask or singleInstance! -->
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
@@ -397,12 +398,12 @@
<activity
android:name="org.briarproject.briar.android.logout.ExitActivity"
android:theme="@android:style/Theme.NoDisplay">
android:theme="@style/Theme.AppCompat.NoActionBar">
</activity>
<activity
android:name=".android.logout.HideUiActivity"
android:theme="@android:style/Theme.NoDisplay">
android:theme="@style/Theme.AppCompat.NoActionBar">
</activity>
<activity

View File

@@ -26,6 +26,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.contact.ConversationViewModel;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.android.view.TextInputView;
@@ -164,6 +165,8 @@ public interface AndroidComponent
void inject(TextInputView textInputView);
void inject(ConversationViewModel conversationViewModel);
// Eager singleton load
void inject(AppModule.EagerSingletons init);
}

View File

@@ -107,7 +107,7 @@ public class AppModule {
Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth =
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
appContext, random, eventBus, backoffFactory);
appContext, random, eventBus, clock, backoffFactory);
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
scheduler, appContext, networkManager, locationUtils, eventBus,
torSocketFactory, backoffFactory, resourceProvider,

View File

@@ -24,6 +24,7 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.reporting.DevReportActivity;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
@@ -33,18 +34,28 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.arch.lifecycle.Lifecycle.State.STARTED;
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 java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
/**
* Warning: Some activities don't extend {@link BaseActivity}.
* E.g. {@link DevReportActivity}
*/
public abstract class BaseActivity extends AppCompatActivity
implements DestroyableContext, OnTapFilteredListener {
private final static Logger LOG = getLogger(BaseActivity.class.getName());
@Inject
protected ScreenFilterMonitor screenFilterMonitor;
@@ -113,6 +124,8 @@ public abstract class BaseActivity extends AppCompatActivity
@Override
protected void onStart() {
super.onStart();
if (LOG.isLoggable(INFO))
LOG.info("Starting " + this.getClass().getSimpleName());
for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityStart();
}
@@ -131,6 +144,8 @@ public abstract class BaseActivity extends AppCompatActivity
@Override
protected void onStop() {
super.onStop();
if (LOG.isLoggable(INFO))
LOG.info("Stopping " + this.getClass().getSimpleName());
for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityStop();
}
@@ -143,6 +158,7 @@ public abstract class BaseActivity extends AppCompatActivity
}
public void showNextFragment(BaseFragment f) {
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) return;
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.step_next_in,
R.anim.step_previous_out, R.anim.step_previous_in,

View File

@@ -9,9 +9,9 @@ public interface RequestCodes {
int REQUEST_WRITE_BLOG_POST = 5;
int REQUEST_SHARE_BLOG = 6;
int REQUEST_RINGTONE = 7;
int REQUEST_PERMISSION_CAMERA = 8;
int REQUEST_PERMISSION_CAMERA_LOCATION = 8;
int REQUEST_DOZE_WHITELISTING = 9;
int REQUEST_ENABLE_BLUETOOTH = 10;
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
int REQUEST_UNLOCK = 11;
int REQUEST_KEYGUARD_UNLOCK = 12;

View File

@@ -51,7 +51,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
protected final IdentityManager identityManager;
protected final BlogManager blogManager;
private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>();
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final Map<MessageId, BlogPostHeader> headerCache =
new ConcurrentHashMap<>();
@@ -129,17 +129,17 @@ abstract class BaseControllerImpl extends DbControllerImpl
public void loadBlogPost(BlogPostHeader header,
ResultExceptionHandler<BlogPostItem, DbException> handler) {
String body = bodyCache.get(header.getId());
if (body != null) {
LOG.info("Loaded body from cache");
handler.onResult(new BlogPostItem(header, body));
String text = textCache.get(header.getId());
if (text != null) {
LOG.info("Loaded text from cache");
handler.onResult(new BlogPostItem(header, text));
return;
}
runOnDbThread(() -> {
try {
long start = now();
BlogPostItem item = getItem(header);
logDuration(LOG, "Loading body", start);
logDuration(LOG, "Loading text", start);
handler.onResult(item);
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -200,28 +200,28 @@ abstract class BaseControllerImpl extends DbControllerImpl
@DatabaseExecutor
private BlogPostItem getItem(BlogPostHeader h) throws DbException {
String body;
String text;
if (h instanceof BlogCommentHeader) {
BlogCommentHeader c = (BlogCommentHeader) h;
BlogCommentItem item = new BlogCommentItem(c);
body = getPostBody(item.getPostHeader().getId());
item.setBody(body);
text = getPostText(item.getPostHeader().getId());
item.setText(text);
return item;
} else {
body = getPostBody(h.getId());
return new BlogPostItem(h, body);
text = getPostText(h.getId());
return new BlogPostItem(h, text);
}
}
@DatabaseExecutor
private String getPostBody(MessageId m) throws DbException {
String body = bodyCache.get(m);
if (body == null) {
body = HtmlUtils.clean(blogManager.getPostBody(m), ARTICLE);
bodyCache.put(m, body);
private String getPostText(MessageId m) throws DbException {
String text = textCache.get(m);
if (text == null) {
text = HtmlUtils.clean(blogManager.getPostText(m), ARTICLE);
textCache.put(m, text);
}
//noinspection ConstantConditions
return body;
return text;
}
}

View File

@@ -34,8 +34,8 @@ class BlogCommentItem extends BlogPostItem {
}
}
public void setBody(String body) {
this.body = body;
public void setText(String text) {
this.text = text;
}
@Override

View File

@@ -3,7 +3,7 @@ package org.briarproject.briar.android.blog;
import android.support.annotation.NonNull;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Author.Status;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.blog.BlogPostHeader;
@@ -15,12 +15,13 @@ import javax.annotation.concurrent.NotThreadSafe;
public class BlogPostItem implements Comparable<BlogPostItem> {
private final BlogPostHeader header;
protected String body;
@Nullable
protected String text;
private boolean read;
BlogPostItem(BlogPostHeader header, @Nullable String body) {
BlogPostItem(BlogPostHeader header, @Nullable String text) {
this.header = header;
this.body = body;
this.text = text;
this.read = header.isRead();
}
@@ -40,12 +41,13 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
return header.getAuthor();
}
Status getAuthorStatus() {
return header.getAuthorStatus();
AuthorInfo getAuthorInfo() {
return header.getAuthorInfo();
}
public String getBody() {
return body;
@Nullable
public String getText() {
return text;
}
public boolean isRssFeed() {

View File

@@ -14,7 +14,6 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
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.view.AuthorView;
@@ -41,7 +40,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
private final AuthorView reblogger;
private final AuthorView author;
private final ImageButton reblogButton;
private final TextView body;
private final TextView text;
private final ViewGroup commentContainer;
private final boolean fullText;
@@ -63,7 +62,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
reblogger = v.findViewById(R.id.rebloggerView);
author = v.findViewById(R.id.authorView);
reblogButton = v.findViewById(R.id.commentView);
body = v.findViewById(R.id.bodyView);
text = v.findViewById(R.id.textView);
commentContainer = v.findViewById(R.id.commentContainer);
}
@@ -98,9 +97,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// author and date
BlogPostHeader post = item.getPostHeader();
Author a = post.getAuthor();
author.setAuthor(a);
author.setAuthorStatus(post.getAuthorStatus());
author.setAuthor(post.getAuthor(), post.getAuthorInfo());
author.setDate(post.getTimestamp());
author.setPersona(
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
@@ -111,17 +108,17 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
author.setAuthorNotClickable();
}
// post body
Spanned bodyText = getSpanned(item.getBody());
// post text
Spanned postText = getSpanned(item.getText());
if (fullText) {
body.setText(bodyText);
body.setTextIsSelectable(true);
makeLinksClickable(body, fragmentManager);
text.setText(postText);
text.setTextIsSelectable(true);
makeLinksClickable(text, fragmentManager);
} else {
body.setTextIsSelectable(false);
if (bodyText.length() > TEASER_LENGTH)
bodyText = getTeaser(ctx, bodyText);
body.setText(bodyText);
text.setTextIsSelectable(false);
if (postText.length() > TEASER_LENGTH)
postText = getTeaser(ctx, postText);
text.setText(postText);
}
// reblog button
@@ -143,8 +140,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
private void onBindComment(BlogCommentItem item) {
// reblogger
reblogger.setAuthor(item.getAuthor());
reblogger.setAuthorStatus(item.getAuthorStatus());
reblogger.setAuthor(item.getAuthor(), item.getAuthorInfo());
reblogger.setDate(item.getTimestamp());
if (!fullText) {
reblogger.setAuthorClickable(v -> listener.onAuthorClick(item));
@@ -163,15 +159,14 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
commentContainer, false);
AuthorView author = v.findViewById(R.id.authorView);
TextView body = v.findViewById(R.id.bodyView);
TextView text = v.findViewById(R.id.textView);
author.setAuthor(c.getAuthor());
author.setAuthorStatus(c.getAuthorStatus());
author.setAuthor(c.getAuthor(), c.getAuthorInfo());
author.setDate(c.getTimestamp());
// TODO make author clickable #624
body.setText(c.getComment());
if (fullText) body.setTextIsSelectable(true);
text.setText(c.getComment());
if (fullText) text.setTextIsSelectable(true);
commentContainer.addView(v);
}

View File

@@ -35,7 +35,7 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
public class WriteBlogPostActivity extends BriarActivity
implements OnEditorActionListener, TextInputListener {
@@ -70,7 +70,7 @@ public class WriteBlogPostActivity extends BriarActivity
setContentView(R.layout.activity_write_blog_post);
input = findViewById(R.id.bodyInput);
input = findViewById(R.id.textInput);
input.setSendButtonEnabled(false);
input.addTextChangedListener(new TextWatcher() {
@Override
@@ -132,23 +132,23 @@ public class WriteBlogPostActivity extends BriarActivity
}
@Override
public void onSendClick(String body) {
public void onSendClick(String text) {
// hide publish button, show progress bar
input.hideSoftKeyboard();
input.setVisibility(GONE);
progressBar.setVisibility(VISIBLE);
body = StringUtils.truncateUtf8(body, MAX_BLOG_POST_BODY_LENGTH);
storePost(body);
text = StringUtils.truncateUtf8(text, MAX_BLOG_POST_TEXT_LENGTH);
storePost(text);
}
private void storePost(String body) {
private void storePost(String text) {
runOnDbThread(() -> {
long timestamp = System.currentTimeMillis();
try {
LocalAuthor author = identityManager.getLocalAuthor();
BlogPost p = blogPostFactory
.createBlogPost(groupId, timestamp, null, author, body);
.createBlogPost(groupId, timestamp, null, author, text);
blogManager.addLocalPost(p);
postPublished();
} catch (DbException | GeneralSecurityException

View File

@@ -0,0 +1,75 @@
package org.briarproject.briar.android.contact;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatDialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.briar.R;
public class AliasDialogFragment extends AppCompatDialogFragment {
final static String TAG = AliasDialogFragment.class.getName();
private ConversationViewModel viewModel;
private ContactId contactId;
private EditText aliasEditText;
public static AliasDialogFragment newInstance(ContactId id) {
AliasDialogFragment f = new AliasDialogFragment();
Bundle args = new Bundle();
args.putInt("contactId", id.getInt());
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() == null) throw new IllegalArgumentException();
int contactIdInt = getArguments().getInt("contactId", -1);
if (contactIdInt == -1) throw new IllegalArgumentException();
contactId = new ContactId(contactIdInt);
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
viewModel =
ViewModelProviders.of(getActivity()).get(ConversationViewModel.class);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_alias_dialog, container,
false);
aliasEditText = v.findViewById(R.id.aliasEditText);
Contact contact = viewModel.getContact().getValue();
String alias = contact == null ? null : contact.getAlias();
aliasEditText.setText(alias);
if (alias != null) aliasEditText.setSelection(alias.length());
Button setButton = v.findViewById(R.id.setButton);
setButton.setOnClickListener(v1 -> {
viewModel.setContactAlias(contactId,
aliasEditText.getText().toString());
getDialog().dismiss();
});
Button cancelButton = v.findViewById(R.id.cancelButton);
cancelButton.setOnClickListener(v1 -> getDialog().cancel());
return v;
}
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.contact;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.View;
import org.briarproject.bramble.api.contact.ContactId;
@@ -9,6 +10,7 @@ import org.briarproject.briar.android.util.BriarAdapter;
import javax.annotation.Nullable;
import static android.support.v7.util.SortedList.INVALID_POSITION;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
public abstract class BaseContactListAdapter<I extends ContactItem, VH extends ContactItemViewHolder<I>>
extends BriarAdapter<I, VH> {
@@ -23,15 +25,15 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
}
@Override
public void onBindViewHolder(VH ui, int position) {
public void onBindViewHolder(@NonNull VH ui, int position) {
I item = items.get(position);
ui.bind(item, listener);
}
@Override
public int compare(I c1, I c2) {
return c1.getContact().getAuthor().getName()
.compareTo(c2.getContact().getAuthor().getName());
return getContactDisplayName(c1.getContact())
.compareTo(getContactDisplayName(c2.getContact()));
}
@Override

View File

@@ -16,6 +16,8 @@ import javax.annotation.Nullable;
import im.delight.android.identicons.IdenticonDrawable;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@UiThread
@NotNullByDefault
public class ContactItemViewHolder<I extends ContactItem>
@@ -41,8 +43,7 @@ public class ContactItemViewHolder<I extends ContactItem>
Author author = item.getContact().getAuthor();
avatar.setImageDrawable(
new IdenticonDrawable(author.getId().getBytes()));
String contactName = author.getName();
name.setText(contactName);
name.setText(getContactDisplayName(item.getContact()));
if (bulb != null) {
// online/offline

View File

@@ -10,6 +10,8 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Locale;
import javax.annotation.Nullable;
import static android.support.v4.view.ViewCompat.setTransitionName;
@@ -36,7 +38,7 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
// unread count
int unreadCount = item.getUnreadCount();
if (unreadCount > 0) {
unread.setText(String.valueOf(unreadCount));
unread.setText(String.format(Locale.getDefault(), "%d", unreadCount));
unread.setVisibility(View.VISIBLE);
} else {
unread.setVisibility(View.INVISIBLE);

View File

@@ -1,7 +1,8 @@
package org.briarproject.briar.android.contact;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
@@ -23,7 +24,6 @@ import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
@@ -34,7 +34,6 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
@@ -53,7 +52,7 @@ import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.contact.ConversationAdapter.ConversationListener;
import org.briarproject.briar.android.contact.ConversationVisitor.BodyCache;
import org.briarproject.briar.android.contact.ConversationVisitor.TextCache;
import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
@@ -105,7 +104,7 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRO
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
@@ -113,7 +112,7 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
@ParametersNotNullByDefault
public class ConversationActivity extends BriarActivity
implements EventListener, ConversationListener, TextInputListener,
BodyCache {
TextCache {
public static final String CONTACT_ID = "briar.CONTACT_ID";
@@ -130,9 +129,9 @@ public class ConversationActivity extends BriarActivity
@CryptoExecutor
Executor cryptoExecutor;
private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>();
private final MutableLiveData<String> contactName = new MutableLiveData<>();
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private ConversationViewModel viewModel;
private ConversationVisitor visitor;
private ConversationAdapter adapter;
private Toolbar toolbar;
@@ -166,8 +165,6 @@ public class ConversationActivity extends BriarActivity
private volatile ContactId contactId;
@Nullable
private volatile AuthorId contactAuthorId;
@Nullable
private volatile GroupId messagingGroupId;
@SuppressWarnings("ConstantConditions")
@@ -176,6 +173,9 @@ public class ConversationActivity extends BriarActivity
setSceneTransitionAnimation();
super.onCreate(state);
viewModel =
ViewModelProviders.of(this).get(ConversationViewModel.class);
Intent i = getIntent();
int id = i.getIntExtra(CONTACT_ID, -1);
if (id == -1) throw new IllegalStateException();
@@ -185,16 +185,29 @@ public class ConversationActivity extends BriarActivity
// Custom Toolbar
toolbar = setUpCustomToolbar(true);
if (toolbar != null) {
toolbarAvatar = toolbar.findViewById(R.id.contactAvatar);
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
toolbarTitle = toolbar.findViewById(R.id.contactName);
}
toolbarAvatar = toolbar.findViewById(R.id.contactAvatar);
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
toolbarTitle = toolbar.findViewById(R.id.contactName);
viewModel.getContactAuthorId().observe(this, authorId -> {
toolbarAvatar.setImageDrawable(
new IdenticonDrawable(authorId.getBytes()));
// we only need this once
viewModel.getContactAuthorId().removeObservers(this);
});
viewModel.getContactDisplayName().observe(this, contactName -> {
toolbarTitle.setText(contactName);
});
viewModel.isContactDeleted().observe(this, deleted -> {
if (deleted != null && deleted) finish();
});
viewModel.loadContact(contactId);
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
visitor = new ConversationVisitor(this, this, contactName);
visitor = new ConversationVisitor(this, this,
viewModel.getContactDisplayName());
adapter = new ConversationAdapter(this, this);
list = findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this));
@@ -229,7 +242,19 @@ public class ConversationActivity extends BriarActivity
notificationManager.blockContactNotification(contactId);
notificationManager.clearContactNotification(contactId);
displayContactOnlineStatus();
loadContactDetailsAndMessages();
LiveData<String> contactName = viewModel.getContactDisplayName();
if (contactName.getValue() == null) {
// wait for contact name to be initialized
contactName.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String cName) {
if (cName != null) {
loadMessages();
contactName.removeObserver(this);
}
}
});
} else loadMessages();
list.startPeriodicUpdate();
}
@@ -266,6 +291,10 @@ public class ConversationActivity extends BriarActivity
intent.putExtra(CONTACT_ID, contactId.getInt());
startActivityForResult(intent, REQUEST_INTRODUCTION);
return true;
case R.id.action_set_alias:
AliasDialogFragment.newInstance(contactId).show(
getSupportFragmentManager(), AliasDialogFragment.TAG);
return true;
case R.id.action_social_remove_person:
askToRemoveContact();
return true;
@@ -274,36 +303,6 @@ public class ConversationActivity extends BriarActivity
}
}
private void loadContactDetailsAndMessages() {
runOnDbThread(() -> {
try {
long start = now();
if (contactAuthorId == null) {
Contact contact = contactManager.getContact(contactId);
contactName.postValue(contact.getAuthor().getName());
contactAuthorId = contact.getAuthor().getId();
}
logDuration(LOG, "Loading contact", start);
loadMessages();
displayContactDetails();
} catch (NoSuchContactException e) {
finishOnUiThread();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
// contactAuthorId and contactName are expected to be set
private void displayContactDetails() {
runOnUiThreadUnlessDestroyed(() -> {
//noinspection ConstantConditions
toolbarAvatar.setImageDrawable(
new IdenticonDrawable(contactAuthorId.getBytes()));
toolbarTitle.setText(contactName.getValue());
});
}
private void displayContactOnlineStatus() {
runOnUiThreadUnlessDestroyed(() -> {
if (connectionRegistry.isConnected(contactId)) {
@@ -370,28 +369,28 @@ public class ConversationActivity extends BriarActivity
return items;
}
private void loadMessageBody(MessageId m) {
private void loadMessageText(MessageId m) {
runOnDbThread(() -> {
try {
long start = now();
String body = messagingManager.getMessageBody(m);
logDuration(LOG, "Loading body", start);
displayMessageBody(m, body);
String text = messagingManager.getMessageText(m);
logDuration(LOG, "Loading text", start);
displayMessageText(m, text);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayMessageBody(MessageId m, String body) {
private void displayMessageText(MessageId m, String text) {
runOnUiThreadUnlessDestroyed(() -> {
bodyCache.put(m, body);
textCache.put(m, text);
SparseArray<ConversationItem> messages =
adapter.getPrivateMessages();
for (int i = 0; i < messages.size(); i++) {
ConversationItem item = messages.valueAt(i);
if (item.getId().equals(m)) {
item.setBody(body);
item.setText(text);
adapter.notifyItemChanged(messages.keyAt(i));
list.scrollToPosition(adapter.getItemCount() - 1);
return;
@@ -453,15 +452,17 @@ public class ConversationActivity extends BriarActivity
private void onNewPrivateMessage(PrivateMessageHeader h) {
runOnUiThreadUnlessDestroyed(() -> {
if (h instanceof PrivateRequest || h instanceof PrivateResponse) {
String cName = contactName.getValue();
String cName = viewModel.getContactDisplayName().getValue();
if (cName == null) {
// Wait for the contact name to be loaded
contactName.observe(this, new Observer<String>() {
viewModel.getContactDisplayName()
.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String cName) {
if (cName != null) {
addConversationItem(h.accept(visitor));
contactName.removeObserver(this);
viewModel.getContactDisplayName()
.removeObserver(this);
}
}
});
@@ -470,7 +471,7 @@ public class ConversationActivity extends BriarActivity
}
} else {
addConversationItem(h.accept(visitor));
loadMessageBody(h.getId());
loadMessageText(h.getId());
}
});
}
@@ -495,8 +496,8 @@ public class ConversationActivity extends BriarActivity
@Override
public void onSendClick(String text) {
if (text.equals("")) return;
text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
if (text.isEmpty()) return;
text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
if (messagingGroupId == null) loadGroupId(text, timestamp);
@@ -510,12 +511,12 @@ public class ConversationActivity extends BriarActivity
return item == null ? 0 : item.getTime() + 1;
}
private void loadGroupId(String body, long timestamp) {
private void loadGroupId(String text, long timestamp) {
runOnDbThread(() -> {
try {
messagingGroupId =
messagingManager.getConversationId(contactId);
createMessage(body, timestamp);
createMessage(text, timestamp);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
@@ -523,19 +524,19 @@ public class ConversationActivity extends BriarActivity
});
}
private void createMessage(String body, long timestamp) {
private void createMessage(String text, long timestamp) {
cryptoExecutor.execute(() -> {
try {
//noinspection ConstantConditions init in loadGroupId()
storeMessage(privateMessageFactory.createPrivateMessage(
messagingGroupId, timestamp, body), body);
messagingGroupId, timestamp, text), text);
} catch (FormatException e) {
throw new RuntimeException(e);
}
});
}
private void storeMessage(PrivateMessage m, String body) {
private void storeMessage(PrivateMessage m, String text) {
runOnDbThread(() -> {
try {
long start = now();
@@ -545,7 +546,7 @@ public class ConversationActivity extends BriarActivity
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
message.getTimestamp(), true, false, false, false);
bodyCache.put(message.getId(), body);
textCache.put(message.getId(), text);
addConversationItem(h.accept(visitor));
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -761,9 +762,9 @@ public class ConversationActivity extends BriarActivity
@Nullable
@Override
public String getBody(MessageId m) {
String body = bodyCache.get(m);
if (body == null) loadMessageBody(m);
return body;
public String getText(MessageId m) {
String text = textCache.get(m);
if (text == null) loadMessageText(m);
return text;
}
}

View File

@@ -14,17 +14,17 @@ import javax.annotation.concurrent.NotThreadSafe;
abstract class ConversationItem {
@Nullable
protected String body;
protected String text;
private final MessageId id;
private final GroupId groupId;
private final long time;
private boolean read;
ConversationItem(MessageId id, GroupId groupId, @Nullable String body,
ConversationItem(MessageId id, GroupId groupId, @Nullable String text,
long time, boolean read) {
this.id = id;
this.groupId = groupId;
this.body = body;
this.text = text;
this.time = time;
this.read = read;
}
@@ -37,13 +37,13 @@ abstract class ConversationItem {
return groupId;
}
void setBody(String body) {
this.body = body;
void setText(String text) {
this.text = text;
}
@Nullable
public String getBody() {
return body;
public String getText() {
return text;
}
long getTime() {

View File

@@ -29,10 +29,10 @@ class ConversationItemViewHolder extends ViewHolder {
@CallSuper
void bind(ConversationItem item) {
if (item.getBody() == null) {
if (item.getText() == null) {
text.setText("\u2026");
} else {
text.setText(StringUtils.trim(item.getBody()));
text.setText(StringUtils.trim(item.getText()));
}
long timestamp = item.getTime();

View File

@@ -29,13 +29,13 @@ class ConversationNoticeInViewHolder extends ConversationItemViewHolder {
ConversationNoticeInItem item =
(ConversationNoticeInItem) conversationItem;
String message = item.getMsgText();
if (StringUtils.isNullOrEmpty(message)) {
String text = item.getMsgText();
if (StringUtils.isNullOrEmpty(text)) {
msgText.setVisibility(GONE);
layout.setBackgroundResource(R.drawable.notice_in);
} else {
msgText.setVisibility(VISIBLE);
msgText.setText(StringUtils.trim(message));
msgText.setText(StringUtils.trim(text));
layout.setBackgroundResource(R.drawable.notice_in_bottom);
}
}

View File

@@ -20,7 +20,7 @@ class ConversationNoticeOutItem extends ConversationOutItem {
ConversationNoticeOutItem(String text, PrivateRequest r) {
super(r.getId(), r.getGroupId(), text, r.getTimestamp(), r.isSent(),
r.isSeen());
this.msgText = r.getMessage();
this.msgText = r.getText();
}
ConversationNoticeOutItem(String text, PrivateResponse r) {

View File

@@ -29,13 +29,13 @@ class ConversationNoticeOutViewHolder extends ConversationOutItemViewHolder {
ConversationNoticeOutItem item =
(ConversationNoticeOutItem) conversationItem;
String message = item.getMsgText();
if (StringUtils.isNullOrEmpty(message)) {
String text = item.getMsgText();
if (StringUtils.isNullOrEmpty(text)) {
msgText.setVisibility(GONE);
layout.setBackgroundResource(R.drawable.notice_out);
} else {
msgText.setVisibility(VISIBLE);
msgText.setText(StringUtils.trim(message));
msgText.setText(StringUtils.trim(text));
layout.setBackgroundResource(R.drawable.notice_out_bottom);
}
}

View File

@@ -27,7 +27,7 @@ class ConversationRequestItem extends ConversationNoticeInItem {
private boolean answered;
ConversationRequestItem(String text, RequestType type, PrivateRequest r) {
super(r.getId(), r.getGroupId(), text, r.getMessage(),
super(r.getId(), r.getGroupId(), text, r.getText(),
r.getTimestamp(), r.isRead());
this.requestType = type;
this.sessionId = r.getSessionId();

View File

@@ -0,0 +1,100 @@
package org.briarproject.briar.android.contact;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Transformations;
import android.support.annotation.NonNull;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.briar.android.AndroidComponent;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.util.UiUtils;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
public class ConversationViewModel extends AndroidViewModel {
private static Logger LOG =
Logger.getLogger(ConversationViewModel.class.getName());
@Inject
@DatabaseExecutor
Executor dbExecutor;
@Inject
ContactManager contactManager;
private final MutableLiveData<Contact> contact = new MutableLiveData<>();
private final LiveData<AuthorId> contactAuthorId =
Transformations.map(contact, c -> c.getAuthor().getId());
private final LiveData<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName);
private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>();
public ConversationViewModel(@NonNull Application application) {
super(application);
AndroidComponent component =
((BriarApplication) application).getApplicationComponent();
component.inject(this);
contactDeleted.setValue(false);
}
void loadContact(ContactId contactId) {
dbExecutor.execute(() -> {
try {
long start = now();
contact.postValue(contactManager.getContact(contactId));
logDuration(LOG, "Loading contact", start);
} catch (NoSuchContactException e) {
contactDeleted.postValue(true);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
void setContactAlias(ContactId contactId, String alias) {
dbExecutor.execute(() -> {
try {
contactManager.setContactAlias(contactId,
alias.isEmpty() ? null : alias);
loadContact(contactId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
LiveData<Contact> getContact() {
return contact;
}
LiveData<AuthorId> getContactAuthorId() {
return contactAuthorId;
}
LiveData<String> getContactDisplayName() {
return contactName;
}
LiveData<Boolean> isContactDeleted() {
return contactDeleted;
}
}

View File

@@ -24,19 +24,20 @@ import static org.briarproject.briar.android.contact.ConversationRequestItem.Req
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.FORUM;
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.GROUP;
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.INTRODUCTION;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@UiThread
@NotNullByDefault
class ConversationVisitor implements PrivateMessageVisitor<ConversationItem> {
private final Context ctx;
private final BodyCache bodyCache;
private final TextCache textCache;
private final LiveData<String> contactName;
ConversationVisitor(Context ctx, BodyCache bodyCache,
ConversationVisitor(Context ctx, TextCache textCache,
LiveData<String> contactName) {
this.ctx = ctx;
this.bodyCache = bodyCache;
this.textCache = textCache;
this.contactName = contactName;
}
@@ -45,8 +46,8 @@ class ConversationVisitor implements PrivateMessageVisitor<ConversationItem> {
ConversationItem item;
if (h.isLocal()) item = new ConversationMessageOutItem(h);
else item = new ConversationMessageInItem(h);
String body = bodyCache.getBody(h.getId());
if (body != null) item.setBody(body);
String text = textCache.getText(h.getId());
if (text != null) item.setText(text);
return item;
}
@@ -188,33 +189,36 @@ class ConversationVisitor implements PrivateMessageVisitor<ConversationItem> {
@Override
public ConversationItem visitIntroductionRequest(IntroductionRequest r) {
String name = getContactDisplayName(r.getNameable(), r.getAlias());
if (r.isLocal()) {
String text = ctx.getString(R.string.introduction_request_sent,
contactName.getValue(), r.getName());
contactName.getValue(), name);
return new ConversationNoticeOutItem(text, r);
} else {
String text = ctx.getString(R.string.introduction_request_received,
contactName.getValue(), r.getName());
contactName.getValue(), name);
return new ConversationRequestItem(text, INTRODUCTION, r);
}
}
@Override
public ConversationItem visitIntroductionResponse(IntroductionResponse r) {
String introducedAuthor =
getContactDisplayName(r.getIntroducedAuthor(),
r.getIntroducedAuthorInfo().getAlias());
if (r.isLocal()) {
String text;
if (r.wasAccepted()) {
String introducee = r.getIntroducedAuthor().getName();
text = ctx.getString(
R.string.introduction_response_accepted_sent,
introducee)
introducedAuthor)
+ "\n\n" + ctx.getString(
R.string.introduction_response_accepted_sent_info,
introducee);
introducedAuthor);
} else {
text = ctx.getString(
R.string.introduction_response_declined_sent,
r.getIntroducedAuthor().getName());
introducedAuthor);
}
return new ConversationNoticeOutItem(text, r);
} else {
@@ -223,24 +227,24 @@ class ConversationVisitor implements PrivateMessageVisitor<ConversationItem> {
text = ctx.getString(
R.string.introduction_response_accepted_received,
contactName.getValue(),
r.getIntroducedAuthor().getName());
introducedAuthor);
} else if (r.isIntroducer()) {
text = ctx.getString(
R.string.introduction_response_declined_received,
contactName.getValue(),
r.getIntroducedAuthor().getName());
introducedAuthor);
} else {
text = ctx.getString(
R.string.introduction_response_declined_received_by_introducee,
contactName.getValue(),
r.getIntroducedAuthor().getName());
introducedAuthor);
}
return new ConversationNoticeInItem(text, r);
}
}
interface BodyCache {
interface TextCache {
@Nullable
String getBody(MessageId m);
String getText(MessageId m);
}
}

View File

@@ -33,7 +33,7 @@ import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_FORUM;
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -130,8 +130,8 @@ public class ForumActivity extends
}
@Override
protected int getMaxBodyLength() {
return MAX_FORUM_POST_BODY_LENGTH;
protected int getMaxTextLength() {
return MAX_FORUM_POST_TEXT_LENGTH;
}
@Override

View File

@@ -79,7 +79,7 @@ class ForumControllerImpl extends
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
if (f.getGroupId().equals(getGroupId())) {
LOG.info("Forum post received, adding...");
onForumPostReceived(f.getHeader(), f.getBody());
onForumPostReceived(f.getHeader(), f.getText());
}
} else if (e instanceof ForumInvitationResponseReceivedEvent) {
ForumInvitationResponseReceivedEvent f =
@@ -109,8 +109,8 @@ class ForumControllerImpl extends
}
@Override
protected String loadMessageBody(ForumPostHeader h) throws DbException {
return forumManager.getPostBody(h.getId());
protected String loadMessageText(ForumPostHeader h) throws DbException {
return forumManager.getPostText(h.getId());
}
@Override
@@ -137,7 +137,7 @@ class ForumControllerImpl extends
}
@Override
public void createAndStoreMessage(String body,
public void createAndStoreMessage(String text,
@Nullable ForumItem parentItem,
ResultExceptionHandler<ForumItem, DbException> handler) {
runOnDbThread(() -> {
@@ -148,7 +148,7 @@ class ForumControllerImpl extends
clock.currentTimeMillis());
MessageId parentId = parentItem != null ?
parentItem.getId() : null;
createMessage(body, timestamp, parentId, author, handler);
createMessage(text, timestamp, parentId, author, handler);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onException(e);
@@ -156,14 +156,14 @@ class ForumControllerImpl extends
});
}
private void createMessage(String body, long timestamp,
private void createMessage(String text, long timestamp,
@Nullable MessageId parentId, LocalAuthor author,
ResultExceptionHandler<ForumItem, DbException> handler) {
cryptoExecutor.execute(() -> {
LOG.info("Creating forum post...");
ForumPost msg = forumManager.createLocalPost(getGroupId(), body,
ForumPost msg = forumManager.createLocalPost(getGroupId(), text,
timestamp, parentId, author);
storePost(msg, body, handler);
storePost(msg, text, handler);
});
}
@@ -179,12 +179,12 @@ class ForumControllerImpl extends
}
@Override
protected ForumItem buildItem(ForumPostHeader header, String body) {
return new ForumItem(header, body);
protected ForumItem buildItem(ForumPostHeader header, String text) {
return new ForumItem(header, text);
}
private void onForumPostReceived(ForumPostHeader h, String body) {
ForumItem item = buildItem(h, body);
private void onForumPostReceived(ForumPostHeader h, String text) {
ForumItem item = buildItem(h, text);
listener.runOnUiThreadUnlessDestroyed(
() -> listener.onItemReceived(item));
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.forum;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Author.Status;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.threaded.ThreadItem;
import org.briarproject.briar.api.forum.ForumPostHeader;
@@ -12,13 +12,13 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
class ForumItem extends ThreadItem {
ForumItem(ForumPostHeader h, String body) {
super(h.getId(), h.getParentId(), body, h.getTimestamp(), h.getAuthor(),
h.getAuthorStatus(), h.isRead());
ForumItem(ForumPostHeader h, String text) {
super(h.getId(), h.getParentId(), text, h.getTimestamp(), h.getAuthor(),
h.getAuthorInfo(), h.isRead());
}
ForumItem(MessageId messageId, @Nullable MessageId parentId, String text,
long timestamp, Author author, Status status) {
long timestamp, Author author, AuthorInfo status) {
super(messageId, parentId, text, timestamp, author, status, true);
}

View File

@@ -38,7 +38,8 @@ import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
public class IntroductionMessageFragment extends BaseFragment
implements TextInputListener {
@@ -148,8 +149,8 @@ public class IntroductionMessageFragment extends BaseFragment
c2.getAuthor().getId().getBytes()));
// set contact names
ui.contactName1.setText(c1.getAuthor().getName());
ui.contactName2.setText(c2.getAuthor().getName());
ui.contactName1.setText(getContactDisplayName(c1));
ui.contactName2.setText(getContactDisplayName(c2));
// hide progress bar
ui.progressBar.setVisibility(GONE);
@@ -187,10 +188,10 @@ public class IntroductionMessageFragment extends BaseFragment
// disable button to prevent accidental double invitations
ui.message.setSendButtonEnabled(false);
String msg = ui.message.getText().toString();
if (msg.equals("")) msg = null;
else msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH);
makeIntroduction(contact1, contact2, msg);
String txt = ui.message.getText().toString();
if (txt.isEmpty()) txt = null;
else txt = StringUtils.truncateUtf8(txt, MAX_INTRODUCTION_TEXT_LENGTH);
makeIntroduction(contact1, contact2, txt);
// don't wait for the introduction to be made before finishing activity
introductionActivity.hideSoftKeyboard(ui.message);
@@ -199,12 +200,12 @@ public class IntroductionMessageFragment extends BaseFragment
}
private void makeIntroduction(Contact c1, Contact c2,
@Nullable String msg) {
@Nullable String text) {
introductionActivity.runOnDbThread(() -> {
// actually make the introduction
try {
long timestamp = System.currentTimeMillis();
introductionManager.makeIntroduction(c1, c2, msg, timestamp);
introductionManager.makeIntroduction(c1, c2, text, timestamp);
} catch (DbException e) {
logException(LOG, WARNING, e);
introductionError();

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.keyagreement;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
@@ -11,19 +10,15 @@ import android.support.annotation.StringRes;
import android.support.annotation.UiThread;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.widget.Toast;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
import org.briarproject.briar.R;
import org.briarproject.briar.R.string;
import org.briarproject.briar.R.style;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
@@ -37,16 +32,19 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.CAMERA;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -55,7 +53,11 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
KeyAgreementEventListener {
private enum BluetoothState {
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED, DISCOVERABLE
}
private enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
private static final Logger LOG =
@@ -64,8 +66,27 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
@Inject
EventBus eventBus;
private boolean isResumed = false, enableWasRequested = false;
private boolean continueClicked, gotCameraPermission;
/**
* Set to true in onPostResume() and false in onPause(). This prevents the
* QR code fragment from being shown if onRequestPermissionsResult() is
* called while the activity is paused, which could cause a crash due to
* https://issuetracker.google.com/issues/37067655.
*/
private boolean isResumed = false;
/**
* Set to true when the continue button is clicked, and false when the QR
* code fragment is shown. This prevents the QR code fragment from being
* shown automatically before the continue button has been clicked.
*/
private boolean continueClicked = false;
/**
* Records whether the Bluetooth adapter was already enabled before we
* asked for Bluetooth discoverability, so we know whether to broadcast a
* {@link BluetoothEnabledEvent}.
*/
private boolean wasAdapterEnabled = false;
private Permission cameraPermission = Permission.UNKNOWN;
private Permission locationPermission = Permission.UNKNOWN;
private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
private BroadcastReceiver bluetoothReceiver = null;
@@ -85,7 +106,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
if (state == null) {
showInitialFragment(IntroFragment.newInstance());
}
IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
bluetoothReceiver = new BluetoothStateReceiver();
registerReceiver(bluetoothReceiver, filter);
}
@@ -107,20 +130,40 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
}
}
@Override
public void onStart() {
super.onStart();
// Permissions may have been granted manually while we were stopped
cameraPermission = Permission.UNKNOWN;
locationPermission = Permission.UNKNOWN;
}
@Override
protected void onPostResume() {
super.onPostResume();
isResumed = true;
// Workaround for
// https://code.google.com/p/android/issues/detail?id=190966
if (canShowQrCodeFragment()) showQrCodeFragment();
showQrCodeFragmentIfAllowed();
}
private boolean canShowQrCodeFragment() {
return isResumed && continueClicked
&& (SDK_INT < 23 || gotCameraPermission)
&& bluetoothState != BluetoothState.UNKNOWN
&& bluetoothState != BluetoothState.WAITING;
private void showQrCodeFragmentIfAllowed() {
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
if (bluetoothState == BluetoothState.UNKNOWN ||
bluetoothState == BluetoothState.ENABLED) {
requestBluetoothDiscoverable();
} else if (bluetoothState != BluetoothState.WAITING) {
showQrCodeFragment();
}
}
}
private boolean areEssentialPermissionsGranted() {
// If the camera permission has been granted, and the location
// permission has been granted or permanently denied, we can continue
return cameraPermission == Permission.GRANTED &&
(locationPermission == Permission.GRANTED ||
locationPermission == Permission.PERMANENTLY_DENIED);
}
@Override
@@ -132,50 +175,54 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
@Override
public void showNextScreen() {
continueClicked = true;
if (checkPermissions()) {
if (shouldRequestEnableBluetooth()) requestEnableBluetooth();
else if (canShowQrCodeFragment()) showQrCodeFragment();
}
if (checkPermissions()) showQrCodeFragmentIfAllowed();
}
private boolean shouldRequestEnableBluetooth() {
return bluetoothState == BluetoothState.UNKNOWN
|| bluetoothState == BluetoothState.REFUSED;
}
private void requestEnableBluetooth() {
private void requestBluetoothDiscoverable() {
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
setBluetoothState(BluetoothState.NO_ADAPTER);
} else if (bt.isEnabled()) {
setBluetoothState(BluetoothState.ENABLED);
} else {
enableWasRequested = true;
setBluetoothState(BluetoothState.WAITING);
Intent i = new Intent(ACTION_REQUEST_ENABLE);
startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH);
wasAdapterEnabled = bt.isEnabled();
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
}
}
private void setBluetoothState(BluetoothState bluetoothState) {
LOG.info("Setting Bluetooth state to " + bluetoothState);
this.bluetoothState = bluetoothState;
if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) {
if (!wasAdapterEnabled && bluetoothState == BluetoothState.ENABLED) {
eventBus.broadcast(new BluetoothEnabledEvent());
enableWasRequested = false;
wasAdapterEnabled = true;
}
if (canShowQrCodeFragment()) showQrCodeFragment();
showQrCodeFragmentIfAllowed();
}
@Override
public void onActivityResult(int request, int result, Intent data) {
// If the request was granted we'll catch the state change event
if (request == REQUEST_ENABLE_BLUETOOTH && result == RESULT_CANCELED)
setBluetoothState(BluetoothState.REFUSED);
if (request == REQUEST_BLUETOOTH_DISCOVERABLE) {
if (result == RESULT_CANCELED) {
setBluetoothState(BluetoothState.REFUSED);
} else {
// If Bluetooth is already discoverable, show the QR code -
// otherwise wait for the state or scan mode to change
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) throw new AssertionError();
if (bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
setBluetoothState(BluetoothState.DISCOVERABLE);
}
}
}
private void showQrCodeFragment() {
// If we return to the intro fragment, the continue button needs to be
// clicked again before showing the QR code fragment
continueClicked = false;
// If we return to the intro fragment, ask for Bluetooth
// discoverability again before showing the QR code fragment
bluetoothState = BluetoothState.UNKNOWN;
// FIXME #824
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
@@ -194,74 +241,113 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
}
private boolean checkPermissions() {
if (ContextCompat.checkSelfPermission(this, CAMERA) !=
PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
CAMERA)) {
OnClickListener continueListener =
(dialog, which) -> requestPermission();
Builder builder = new Builder(this, style.BriarDialogTheme);
builder.setTitle(string.permission_camera_title);
builder.setMessage(string.permission_camera_request_body);
builder.setNeutralButton(string.continue_button,
continueListener);
builder.show();
} else {
requestPermission();
}
gotCameraPermission = false;
if (areEssentialPermissionsGranted()) return true;
// If the camera permission has been permanently denied, ask the
// user to change the setting
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(R.string.permission_camera_title);
builder.setMessage(R.string.permission_camera_denied_body);
builder.setPositiveButton(R.string.ok,
UiUtils.getGoToSettingsListener(this));
builder.setNegativeButton(R.string.cancel,
(dialog, which) -> supportFinishAfterTransition());
builder.show();
return false;
} else {
gotCameraPermission = true;
return true;
}
// Should we show the rationale for one or both permissions?
if (cameraPermission == Permission.SHOW_RATIONALE &&
locationPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_camera_location_title,
R.string.permission_camera_location_request_body);
} else if (cameraPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_camera_title,
R.string.permission_camera_request_body);
} else if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_location_title,
R.string.permission_location_request_body);
} else {
requestPermissions();
}
return false;
}
private void requestPermission() {
ActivityCompat.requestPermissions(this, new String[] {CAMERA},
REQUEST_PERMISSION_CAMERA);
private void showRationale(@StringRes int title, @StringRes int body) {
Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(title);
builder.setMessage(body);
builder.setNeutralButton(R.string.continue_button,
(dialog, which) -> requestPermissions());
builder.show();
}
private void requestPermissions() {
ActivityCompat.requestPermissions(this,
new String[] {CAMERA, ACCESS_COARSE_LOCATION},
REQUEST_PERMISSION_CAMERA_LOCATION);
}
@Override
@UiThread
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
if (requestCode == REQUEST_PERMISSION_CAMERA) {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 &&
grantResults[0] == PERMISSION_GRANTED) {
gotCameraPermission = true;
showNextScreen();
} else {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
CAMERA)) {
// The user has permanently denied the request
OnClickListener cancelListener =
(dialog, which) -> supportFinishAfterTransition();
Builder builder = new Builder(this, style.BriarDialogTheme);
builder.setTitle(string.permission_camera_title);
builder.setMessage(string.permission_camera_denied_body);
builder.setPositiveButton(string.ok,
UiUtils.getGoToSettingsListener(this));
builder.setNegativeButton(string.cancel, cancelListener);
builder.show();
} else {
Toast.makeText(this, string.permission_camera_denied_toast,
LENGTH_LONG).show();
supportFinishAfterTransition();
}
}
String[] permissions, int[] grantResults) {
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
throw new AssertionError();
if (gotPermission(CAMERA, permissions, grantResults)) {
cameraPermission = Permission.GRANTED;
} else if (shouldShowRationale(CAMERA)) {
cameraPermission = Permission.SHOW_RATIONALE;
} else {
cameraPermission = Permission.PERMANENTLY_DENIED;
}
if (gotPermission(ACCESS_COARSE_LOCATION, permissions, grantResults)) {
locationPermission = Permission.GRANTED;
} else if (shouldShowRationale(ACCESS_COARSE_LOCATION)) {
locationPermission = Permission.SHOW_RATIONALE;
} else {
locationPermission = Permission.PERMANENTLY_DENIED;
}
// If a permission dialog has been shown, showing the QR code fragment
// on this call path would cause a crash due to
// https://code.google.com/p/android/issues/detail?id=190966.
// In that case the isResumed flag prevents the fragment from being
// shown here, and showQrCodeFragmentIfAllowed() will be called again
// from onPostResume().
if (checkPermissions()) showQrCodeFragmentIfAllowed();
}
private boolean gotPermission(String permission, String[] permissions,
int[] grantResults) {
for (int i = 0; i < permissions.length; i++) {
if (permission.equals(permissions[i]))
return grantResults[i] == PERMISSION_GRANTED;
}
return false;
}
private boolean shouldShowRationale(String permission) {
return ActivityCompat.shouldShowRequestPermissionRationale(this,
permission);
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON) setBluetoothState(BluetoothState.ENABLED);
else setBluetoothState(BluetoothState.UNKNOWN);
String action = intent.getAction();
if (ACTION_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON)
setBluetoothState(BluetoothState.ENABLED);
else setBluetoothState(BluetoothState.UNKNOWN);
} else if (ACTION_SCAN_MODE_CHANGED.equals(action)) {
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
setBluetoothState(BluetoothState.DISCOVERABLE);
else if (scanMode == SCAN_MODE_CONNECTABLE)
setBluetoothState(BluetoothState.ENABLED);
else setBluetoothState(BluetoothState.UNKNOWN);
}
}
}
}

View File

@@ -20,6 +20,7 @@ import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Inject;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
@@ -34,7 +35,7 @@ public class OpenDatabaseActivity extends BriarActivity
private TextView textView;
private ImageView imageView;
private boolean showingMigration = false;
private boolean showingMigration = false, showingCompaction = false;
@Override
public void onCreate(@Nullable Bundle state) {
@@ -57,6 +58,7 @@ public class OpenDatabaseActivity extends BriarActivity
finishAndStartApp();
} else {
if (state == MIGRATING_DATABASE) showMigration();
else if (state == COMPACTING_DATABASE) showCompaction();
eventBus.addListener(this);
}
}
@@ -75,6 +77,8 @@ public class OpenDatabaseActivity extends BriarActivity
runOnUiThreadUnlessDestroyed(this::finishAndStartApp);
else if (state == MIGRATING_DATABASE)
runOnUiThreadUnlessDestroyed(this::showMigration);
else if (state == COMPACTING_DATABASE)
runOnUiThreadUnlessDestroyed(this::showCompaction);
}
}
@@ -85,6 +89,13 @@ public class OpenDatabaseActivity extends BriarActivity
showingMigration = true;
}
private void showCompaction() {
if (showingCompaction) return;
textView.setText(R.string.startup_compact_database);
imageView.setImageResource(R.drawable.startup_migration);
showingCompaction = true;
}
private void finishAndStartApp() {
startActivity(new Intent(this, NavDrawerActivity.class));
supportFinishAfterTransition();

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.panic;
import android.app.Activity;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
@@ -22,9 +21,14 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import info.guardianproject.panic.Panic;
import info.guardianproject.panic.PanicResponder;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.ACTION_VIEW;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static info.guardianproject.panic.Panic.PACKAGE_NAME_NONE;
public class PanicPreferencesFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -42,7 +46,9 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
@Override
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.panic_preferences);
}
private void updatePreferences() {
pm = getActivity().getPackageManager();
lockPref = (SwitchPreference) findPreference(KEY_LOCK);
@@ -74,7 +80,7 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
ArrayList<CharSequence> entries = new ArrayList<>();
ArrayList<CharSequence> entryValues = new ArrayList<>();
entries.add(0, getString(R.string.panic_app_setting_none));
entryValues.add(0, Panic.PACKAGE_NAME_NONE);
entryValues.add(0, PACKAGE_NAME_NONE);
for (ResolveInfo resolveInfo : PanicResponder.resolveTriggerApps(pm)) {
if (resolveInfo.activityInfo == null)
@@ -83,21 +89,19 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
entryValues.add(resolveInfo.activityInfo.packageName);
}
panicAppPref.setEntries(
entries.toArray(new CharSequence[entries.size()]));
panicAppPref.setEntryValues(
entryValues.toArray(new CharSequence[entryValues.size()]));
panicAppPref.setDefaultValue(Panic.PACKAGE_NAME_NONE);
panicAppPref.setEntries(entries.toArray(new CharSequence[0]));
panicAppPref.setEntryValues(entryValues.toArray(new CharSequence[0]));
panicAppPref.setDefaultValue(PACKAGE_NAME_NONE);
panicAppPref.setOnPreferenceChangeListener((preference, newValue) -> {
String packageName = (String) newValue;
PanicResponder.setTriggerPackageName(getActivity(), packageName);
showPanicApp(packageName);
if (packageName.equals(Panic.PACKAGE_NAME_NONE)) {
if (packageName.equals(PACKAGE_NAME_NONE)) {
purgePref.setChecked(false);
purgePref.setEnabled(false);
getActivity().setResult(Activity.RESULT_CANCELED);
getActivity().setResult(RESULT_CANCELED);
} else {
purgePref.setEnabled(true);
}
@@ -107,16 +111,18 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
if (entries.size() <= 1) {
panicAppPref.setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
Intent intent = new Intent(ACTION_VIEW);
intent.setData(Uri.parse(
"market://details?id=info.guardianproject.ripple"));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
if (intent.resolveActivity(getActivity().getPackageManager())
!= null) {
startActivity(intent);
}
return true;
});
} else {
panicAppPref.setOnPreferenceClickListener(null);
}
}
@@ -125,6 +131,7 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
super.onStart();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
updatePreferences();
showPanicApp(PanicResponder.getTriggerPackageName(getActivity()));
}
@@ -152,9 +159,9 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
private void showPanicApp(String triggerPackageName) {
if (TextUtils.isEmpty(triggerPackageName)
|| triggerPackageName.equals(Panic.PACKAGE_NAME_NONE)) {
|| triggerPackageName.equals(PACKAGE_NAME_NONE)) {
// no panic app set
panicAppPref.setValue(Panic.PACKAGE_NAME_NONE);
panicAppPref.setValue(PACKAGE_NAME_NONE);
panicAppPref
.setSummary(getString(R.string.panic_app_setting_summary));
panicAppPref.setIcon(
@@ -176,8 +183,8 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
} catch (PackageManager.NameNotFoundException e) {
// revert back to no app, just to be safe
PanicResponder.setTriggerPackageName(getActivity(),
Panic.PACKAGE_NAME_NONE);
showPanicApp(Panic.PACKAGE_NAME_NONE);
PACKAGE_NAME_NONE);
showPanicApp(PACKAGE_NAME_NONE);
}
}
}
@@ -186,10 +193,10 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
DialogInterface.OnClickListener okListener = (dialog, which) -> {
PanicResponder.setTriggerPackageName(getActivity());
showPanicApp(PanicResponder.getTriggerPackageName(getActivity()));
getActivity().setResult(Activity.RESULT_OK);
getActivity().setResult(RESULT_OK);
};
DialogInterface.OnClickListener cancelListener = (dialog, which) -> {
getActivity().setResult(Activity.RESULT_CANCELED);
getActivity().setResult(RESULT_CANCELED);
getActivity().finish();
};

View File

@@ -37,7 +37,7 @@ import javax.inject.Inject;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_TEXT_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -179,8 +179,8 @@ public class GroupActivity extends
}
@Override
protected int getMaxBodyLength() {
return MAX_GROUP_POST_BODY_LENGTH;
protected int getMaxTextLength() {
return MAX_GROUP_POST_TEXT_LENGTH;
}
@Override

View File

@@ -84,7 +84,7 @@ class GroupControllerImpl extends
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
if (!g.isLocal() && g.getGroupId().equals(getGroupId())) {
LOG.info("Group message received, adding...");
GroupMessageItem item = buildItem(g.getHeader(), g.getBody());
GroupMessageItem item = buildItem(g.getHeader(), g.getText());
listener.runOnUiThreadUnlessDestroyed(
() -> listener.onItemReceived(item));
}
@@ -124,13 +124,13 @@ class GroupControllerImpl extends
}
@Override
protected String loadMessageBody(GroupMessageHeader header)
protected String loadMessageText(GroupMessageHeader header)
throws DbException {
if (header instanceof JoinMessageHeader) {
// will be looked up later
return "";
}
return privateGroupManager.getMessageBody(header.getId());
return privateGroupManager.getMessageText(header.getId());
}
@Override
@@ -159,7 +159,7 @@ class GroupControllerImpl extends
}
@Override
public void createAndStoreMessage(String body,
public void createAndStoreMessage(String text,
@Nullable GroupMessageItem parentItem,
ResultExceptionHandler<GroupMessageItem, DbException> handler) {
runOnDbThread(() -> {
@@ -173,7 +173,7 @@ class GroupControllerImpl extends
long timestamp = count.getLatestMsgTime();
if (parentItem != null) parentId = parentItem.getId();
timestamp = max(clock.currentTimeMillis(), timestamp + 1);
createMessage(body, timestamp, parentId, author, previousMsgId,
createMessage(text, timestamp, parentId, author, previousMsgId,
handler);
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -182,7 +182,7 @@ class GroupControllerImpl extends
});
}
private void createMessage(String body, long timestamp,
private void createMessage(String text, long timestamp,
@Nullable MessageId parentId, LocalAuthor author,
MessageId previousMsgId,
ResultExceptionHandler<GroupMessageItem, DbException> handler) {
@@ -190,8 +190,8 @@ class GroupControllerImpl extends
LOG.info("Creating group message...");
GroupMessage msg = groupMessageFactory
.createGroupMessage(getGroupId(), timestamp,
parentId, author, body, previousMsgId);
storePost(msg, body, handler);
parentId, author, text, previousMsgId);
storePost(msg, text, handler);
});
}
@@ -208,11 +208,11 @@ class GroupControllerImpl extends
@Override
protected GroupMessageItem buildItem(GroupMessageHeader header,
String body) {
String text) {
if (header instanceof JoinMessageHeader) {
return new JoinMessageItem((JoinMessageHeader) header, body);
return new JoinMessageItem((JoinMessageHeader) header, text);
}
return new GroupMessageItem(header, body);
return new GroupMessageItem(header, text);
}
@Override

View File

@@ -4,7 +4,7 @@ import android.support.annotation.LayoutRes;
import android.support.annotation.UiThread;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Author.Status;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
@@ -22,14 +22,14 @@ class GroupMessageItem extends ThreadItem {
private GroupMessageItem(MessageId messageId, GroupId groupId,
@Nullable MessageId parentId, String text, long timestamp,
Author author, Status status, boolean isRead) {
Author author, AuthorInfo status, boolean isRead) {
super(messageId, parentId, text, timestamp, author, status, isRead);
this.groupId = groupId;
}
GroupMessageItem(GroupMessageHeader h, String text) {
this(h.getId(), h.getGroupId(), h.getParentId(), text, h.getTimestamp(),
h.getAuthor(), h.getAuthorStatus(), h.isRead());
h.getAuthor(), h.getAuthorInfo(), h.isRead());
}
public GroupId getGroupId() {

View File

@@ -9,7 +9,8 @@ import org.briarproject.briar.R;
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.AuthorInfo.Status.OURSELVES;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@UiThread
@NotNullByDefault
@@ -36,24 +37,27 @@ class JoinMessageItemViewHolder
if (item.isInitial()) {
textView.setText(R.string.groups_member_created_you);
} else {
textView.setText(
getContext().getString(R.string.groups_member_joined,
item.getAuthor().getName()));
String name = getContactDisplayName(item.getAuthor(),
item.getAuthorInfo().getAlias());
textView.setText(getContext()
.getString(R.string.groups_member_joined, name));
}
}
private void bind(JoinMessageItem item) {
Context ctx = getContext();
String name = getContactDisplayName(item.getAuthor(),
item.getAuthorInfo().getAlias());
if (item.isInitial()) {
textView.setText(ctx.getString(R.string.groups_member_created,
item.getAuthor().getName()));
textView.setText(
ctx.getString(R.string.groups_member_created, name));
} else {
if (item.getStatus() == OURSELVES) {
if (item.getAuthorInfo().getStatus() == OURSELVES) {
textView.setText(R.string.groups_member_joined_you);
} else {
textView.setText(ctx.getString(R.string.groups_member_joined,
item.getAuthor().getName()));
textView.setText(
ctx.getString(R.string.groups_member_joined, name));
}
}
}

View File

@@ -18,6 +18,6 @@ public interface CreateGroupController
ResultExceptionHandler<GroupId, DbException> result);
void sendInvitation(GroupId g, Collection<ContactId> contacts,
String message, ResultExceptionHandler<Void, DbException> result);
String text, ResultExceptionHandler<Void, DbException> result);
}

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