Compare commits

...

138 Commits

Author SHA1 Message Date
akwizgran
9d2cc4c3c1 Add method to detect whether system uses glibc. 2021-02-20 10:18:24 +00:00
akwizgran
8a534b4503 Bump version numbers for 1.2.16 release. 2021-02-19 18:01:56 +00:00
akwizgran
e5b2275c82 Merge branch '1947-forum-crash' into 'master'
Don't add new thread items when the existing ones haven't loaded

Closes #1947

See merge request briar/briar!1375
2021-02-19 17:27:38 +00:00
Torsten Grote
5159593825 Don't add new item when the existing ones haven't loaded 2021-02-19 14:17:21 -03:00
akwizgran
6fbc82ee27 Merge branch '1075-1146-1317-ongoing-notification' into 'master'
Use IMPORTANCE_LOW for ongoing notification, don't show a badge

Closes #1317, #1146, and #1075

See merge request briar/briar!1369
2021-02-18 17:00:47 +00:00
akwizgran
885b03cfd7 Bump version numbers for 1.2.15 release. 2021-02-18 15:27:57 +00:00
akwizgran
f81bfcafeb Update translations. 2021-02-18 15:26:10 +00:00
akwizgran
f36f1cf3d4 Merge branch '1764-fix-change-app-language-does-not-work' into 'master'
Resolve "Change app language does not work"

Closes #1764

See merge request briar/briar!1367
2021-02-17 16:59:59 +00:00
Torsten Grote
7d6a63d866 Merge branch '1934-upgrade-obfs4proxy' into 'master'
Upgrade obfs4proxy to 0.0.12-dev

Closes #1934

See merge request briar/briar!1372
2021-02-17 16:58:22 +00:00
akwizgran
15ebdf8dd5 Upgrade obfs4proxy to 0.0.12-dev. 2021-02-17 16:41:49 +00:00
akwizgran
db2c235283 Merge branch 'private-group-disabled' into 'master'
Fix disabled groups after screen rotation

See merge request briar/briar!1371
2021-02-17 14:09:57 +00:00
Sebastian Kürten
e5bd43469e Add Javados to Localizer#setLocale() 2021-02-15 14:54:20 +01:00
Torsten Grote
9366c184d8 Fix disabled groups after screen rotation
isDissolved was reverted to LiveData that only shows a dialog when the activity was first opened
2021-02-15 09:55:59 -03:00
Sebastian Kürten
73d2c964d4 Make language switching for robust 2021-02-15 12:31:51 +01:00
akwizgran
fb2b4209cf Use IMPORTANCE_LOW for ongoing notification, don't show a badge. 2021-02-10 11:46:41 +00:00
Torsten Grote
a04b512497 Merge branch 'tor-0.3.5.13' into 'master'
Upgrade Tor to 0.3.5.13

Closes #1922

See merge request briar/briar!1363
2021-02-09 12:15:45 +00:00
akwizgran
3d9515e308 Also upgrade obfs4proxy and bramble-java's Tor. 2021-02-09 12:05:54 +00:00
akwizgran
1b19b331b1 Merge branch '1904-fragment-started-too-late' into 'master'
Don't launch fragments with back button when not started

Closes #1904

See merge request briar/briar!1365
2021-02-09 11:05:08 +00:00
akwizgran
d151a2d7f7 Merge branch '1910-state-exception-when-adding-contact' into 'master'
Restore remote handshake link when AddContactViewModel gets destroyed

Closes #1910

See merge request briar/briar!1364
2021-02-09 10:49:38 +00:00
Torsten Grote
9712a4b849 Don't launch fragments with back button when not started
Sounds strange, but apparently can happen.
2021-02-08 16:38:15 -03:00
Torsten Grote
cf1ac5e3e5 Restore remote handshake link when AddContactViewModel gets destroyed 2021-02-08 16:03:10 -03:00
Torsten Grote
cb859e998d Upgrade Tor to 0.3.5.13 2021-02-08 15:44:35 -03:00
akwizgran
0b9345f867 Merge branch '1621-link-disappearing' into 'master'
Remove monospace typeface from our briar:// link

Closes #1621

See merge request briar/briar!1362
2021-02-08 18:36:16 +00:00
Torsten Grote
12988120d1 Remove monospace typeface from our briar:// link
as this makes the text to become invisible when selecting all text on API 15-17
2021-02-08 14:45:57 -03:00
akwizgran
8d6c866e62 Merge branch '1926-cap-scrypt-cost' into 'master'
Cap the scrypt cost parameter to avoid OOM

Closes #1926

See merge request briar/briar!1360
2021-02-08 17:30:57 +00:00
akwizgran
8f82cf3c73 Merge branch '1917-logcat-process' into 'master'
Fix crash reporter to capture logs from main process

Closes #1917

See merge request briar/briar!1359
2021-02-08 16:58:12 +00:00
Torsten Grote
21112ce092 Encrypt logs before handing them to crash report process 2021-02-08 13:43:37 -03:00
akwizgran
21ee3ea00d Merge branch 'add-custom-dictionary' into 'master'
Add a custom dictionary

See merge request briar/briar!1361
2021-02-08 14:01:43 +00:00
Sebastian Kürten
bb964101b3 Add a custom dictionary
This reduces the amount of words highlighted by the spell checker and
helps focussing on words that are really misspelled.
2021-02-08 14:35:14 +01:00
akwizgran
d796eff0f6 Cap the scrypt cost parameter to avoid OOM. 2021-02-08 11:32:03 +00:00
Torsten Grote
700ea2b387 Add support for logs to StreamReader and StreamWriter
Shamelessly stolen from d9b4c013
2021-02-05 17:07:48 -03:00
Torsten Grote
6e3a7d8d0c Merge branch 'gitlab-bridge-test' into 'master'
Add GitLab pipeline stage for running optional tests

See merge request briar/briar!1353
2021-01-29 16:07:49 +00:00
akwizgran
166b5d4add Run optional tests automatically for tags, otherwise manually. 2021-01-29 15:45:39 +00:00
akwizgran
0fd59a26f6 Raise BridgeTest timeout to avoid spurious failures. 2021-01-29 15:39:59 +00:00
akwizgran
4162bf990a Merge branch '1881-thread-list-controller' into 'master'
Migrate ThreadListController to ViewModel

Closes #1881, #1873, and #1870

See merge request briar/briar!1336
2021-01-29 15:10:16 +00:00
akwizgran
09cfadbf7e Add manual pipeline stage for running optional tests. 2021-01-29 14:38:03 +00:00
akwizgran
3fb27dbb12 Bump version numbers for 1.2.14 release. 2021-01-29 14:10:24 +00:00
Torsten Grote
ae4a04bada Finishing touches of ThreadListViewModel migration
docs and minor improvements
2021-01-29 08:33:28 -03:00
akwizgran
831c65b647 Merge branch 'vector-compat' into 'master'
Use vector support libraries instead of rasterizing all drawables

See merge request briar/briar!1346
2021-01-28 17:59:08 +00:00
akwizgran
afcd38b84c Update translations. 2021-01-28 16:15:44 +00:00
Torsten Grote
d670179e30 Access MessageTree only on UiThread and improve code in the process 2021-01-27 15:37:09 -03:00
Torsten Grote
998c435b13 Allow to add forum/group posts in transaction 2021-01-27 15:37:09 -03:00
Torsten Grote
4a0327a62b thread list: fix redundant load and dissolved dialog showing again after screen rotation 2021-01-27 15:37:08 -03:00
akwizgran
70532732c8 Use commit action to add contacts on UI thread. 2021-01-27 15:37:08 -03:00
akwizgran
d69406dfe3 Add transactional getSharedWith() method to SharingManager. 2021-01-27 15:37:08 -03:00
akwizgran
98619df867 Use commit action to add contacts to SharingController. 2021-01-27 15:37:07 -03:00
akwizgran
f2eca0fdb6 Add transactional getMembers() method to PrivateGroupManager. 2021-01-27 15:37:07 -03:00
akwizgran
c62a57e8b2 Add transactional helper method to DbViewModel. 2021-01-27 15:37:07 -03:00
Torsten Grote
239c4a27ad Address first round of review feedback for thread list view model migration 2021-01-27 15:37:06 -03:00
Torsten Grote
e5d78a858d Clear thread notification automatically after blocking new ones 2021-01-26 15:42:18 -03:00
Torsten Grote
5c1bcdeb9d Merge branch 'update-bridges' into 'master'
Update bridges

See merge request briar/briar!1352
2021-01-26 14:11:00 +00:00
akwizgran
6c1f5450cb Add run configuration for BridgeTest. 2021-01-26 13:57:33 +00:00
akwizgran
0d070cf422 Change dummy address for meek bridge.
See https://gitweb.torproject.org/builders/tor-browser-build.git/commit/projects/tor-browser/Bundle-Data/PTConfigs/bridge_prefs.js?id=8bd845464ae14bf56e0187dfa6f6e773a6593f55
2021-01-26 13:53:51 +00:00
akwizgran
d34d66c691 Update list of obfs4 bridges. 2021-01-26 13:51:41 +00:00
Torsten Grote
6005d156eb Rename ic_lock icon to notification_lock 2021-01-26 08:24:16 -03:00
Torsten Grote
635008fb60 Introduce SharingController with LiveData
and get rid of ThreadList controllers
2021-01-25 14:04:29 -03:00
Torsten Grote
b78569119a Remove Visibility from JoinMessageHeader and Item 2021-01-25 14:04:28 -03:00
Torsten Grote
8372bb01b2 Move marking thread list items read to ViewModel 2021-01-25 14:04:28 -03:00
Torsten Grote
766718e75c Remove text cache as it is no longer needed 2021-01-25 14:04:28 -03:00
Torsten Grote
1c107a851b Move thread list events, fields and notification handling into ViewModels 2021-01-25 14:04:26 -03:00
Torsten Grote
db53e79d1d Remove ForumActivityTest which provided little value anyway 2021-01-25 14:04:17 -03:00
Torsten Grote
21e56284fb Move adding new ThreadList items to ViewModel 2021-01-25 14:04:16 -03:00
Torsten Grote
d393b79ced Submit thread list items to ListAdapter 2021-01-25 14:04:09 -03:00
Torsten Grote
6611d7c02e Move removal of named groups into ViewModel 2021-01-25 14:00:43 -03:00
Torsten Grote
ab43dd4986 Create ThreadListViewModels and move loading of named groups there 2021-01-25 14:00:41 -03:00
Torsten Grote
36a9174781 Perform thread list core access within a single transaction 2021-01-25 14:00:15 -03:00
Torsten Grote
94dd75f24b Use VectorDrawableCompat compatible ways of setting drawables programmatically
so they won't crash on API < 21
2021-01-25 13:34:27 -03:00
Torsten Grote
c93e5441b0 Store rasterized notification icons, because NotificationCompat doesn't handle it
and would crash on API < 21
2021-01-25 13:34:27 -03:00
Torsten Grote
8ec8cc927b Use vector support libraries instead of rasterizing all drawables
https://developer.android.com/guide/topics/graphics/vector-drawable-resources#vector-drawables-backward-solution
2021-01-25 13:34:26 -03:00
akwizgran
4663e727eb Merge branch '214-user-avatars' into 'master'
Merge user avatars feature branch

See merge request briar/briar!1334
2021-01-25 15:15:54 +00:00
akwizgran
e2acd19ffd Trivial code cleanups. 2021-01-25 15:05:15 +00:00
akwizgran
0befa6a823 Use NullSafety.equals(). 2021-01-25 15:05:15 +00:00
Torsten Grote
01083f47ea Merge branch '1865-setupcontroller-to-viewmodel' into 'master'
Migrate SetupController to a ViewModel

See merge request briar/briar!1340
2021-01-25 14:03:32 +00:00
Daniel Lublin
a349bd146c Migrate SetupController to a ViewModel
Solves #1865
2021-01-25 14:34:19 +01:00
Torsten Grote
4ffa9e191c Merge branch '1912-specify-group-id-when-loading-attachment' into '214-user-avatars'
Ensure that attachment has expected group ID when loading

See merge request briar/briar!1347
2021-01-25 12:58:19 +00:00
akwizgran
e616fc3da7 Throw NoSuchMessageException if attachment is invalid. 2021-01-22 14:01:36 +00:00
akwizgran
aed5ac5bb4 Ensure that attachment has expected group ID when loading. 2021-01-22 13:35:06 +00:00
Sebastian Kürten
cae53a9fcc Reorganize MediaModule and AttachmentModule 2021-01-21 10:13:29 -03:00
Sebastian Kürten
6660625ba6 Update avatar in contact list when changed while list is open 2021-01-21 10:13:29 -03:00
Sebastian Kürten
bf9ba13b68 Update app bar in ConversationActivity with received avatar 2021-01-21 09:33:56 -03:00
Sebastian Kürten
a52c97ecf7 Format touched xml layouts 2021-01-21 09:33:56 -03:00
Sebastian Kürten
a2174e7677 SettingsViewModel: use LiveEvent instead of LiveData 2021-01-21 09:33:55 -03:00
Sebastian Kürten
d3cf3d680e Display error message toast when updating profile picture fails 2021-01-21 09:33:55 -03:00
Sebastian Kürten
cbb87aa00c Move compression of image to IoExecutor 2021-01-21 09:33:55 -03:00
Sebastian Kürten
53d985161f Remove layout_gravity without any effect 2021-01-21 09:33:54 -03:00
Sebastian Kürten
86002b0402 Move some findViewById() out of a lambda 2021-01-21 09:33:54 -03:00
Sebastian Kürten
f75e789493 Improve dialog for avatar confirmation 2021-01-21 09:33:54 -03:00
Sebastian Kürten
b22f302fdd Statically import Level.WARNING 2021-01-21 09:33:53 -03:00
Sebastian Kürten
c4a42760c8 Use BriarDialogTheme for avatar confirmation 2021-01-21 09:33:53 -03:00
Sebastian Kürten
8d92f36522 Remove some useless tools:text 2021-01-21 09:33:53 -03:00
Sebastian Kürten
6c86873ea7 Reduce margin verbosity in SettingsActivity 2021-01-21 09:33:52 -03:00
Sebastian Kürten
4fa9d654b5 Eliminate NestedScrollView from SettingsActivity 2021-01-21 09:33:52 -03:00
Sebastian Kürten
3d303ccad5 Natural order of views in SettingsActivity 2021-01-21 09:33:52 -03:00
Sebastian Kürten
b0d99a9f33 Avoid staircase indent 2021-01-21 09:33:51 -03:00
Sebastian Kürten
1a5e789bec Call loadOwnIdentityInfo() in SettingsViewModel's constructor 2021-01-21 09:33:51 -03:00
Sebastian Kürten
97040c6299 Remove a useless method call 2021-01-21 09:33:51 -03:00
Sebastian Kürten
301085c685 Move findViewById() out of callback 2021-01-21 09:33:50 -03:00
Sebastian Kürten
946c79d918 Be consequent with AlertDialog.Builder method usage 2021-01-21 09:33:50 -03:00
Sebastian Kürten
20418cfc7f Rename inflater variable 2021-01-21 09:33:50 -03:00
Sebastian Kürten
7b09f0f98d Rename a string 2021-01-21 09:33:49 -03:00
Sebastian Kürten
97a7c8824b Replace usage of UnsupportedMimeTypeException from jsoup with own type 2021-01-21 09:33:49 -03:00
Sebastian Kürten
423684a14f Reduce visibility of SettingsViewModel 2021-01-21 09:33:48 -03:00
Sebastian Kürten
09d91b522f Fix a warning in SettingsActvitiy 2021-01-21 09:33:48 -03:00
Sebastian Kürten
64c0e9e9e4 Fix a few warnings in ConfirmAvatarDialogFragment 2021-01-21 09:33:48 -03:00
Sebastian Kürten
15021bffef Inline getAttachmentFileIntent() 2021-01-21 09:33:48 -03:00
Sebastian Kürten
43c6ae4258 Implement UI for setting profile pictures 2021-01-21 09:33:47 -03:00
Sebastian Kürten
f819930570 Create ImageCompressor amd ImageCompressorImpl
* Methods from AttachmentCreationTask have been moved into them:
  * compressImage()
  * createBitmap()
* ImageCompressor is availabe via AttachmentModule
2021-01-21 09:33:47 -03:00
Torsten Grote
aa00ba7220 test avatars: get rid of the 1% 2021-01-21 09:33:47 -03:00
Torsten Grote
19db58ee19 Allow the user to configure the percentage of test contacts with avatars 2021-01-21 09:33:46 -03:00
Torsten Grote
05f4d63356 Create test avatars when creating test contacts 2021-01-21 09:33:46 -03:00
Torsten Grote
6e5af2d3d3 Create TestAvatarCreator for use in debug builds only 2021-01-21 09:33:46 -03:00
Torsten Grote
00bf1eac0a Factor out MessageEncoder from AvatarManager 2021-01-21 09:33:45 -03:00
akwizgran
8a10f16861 Deliver test messages as though they arrived from contacts. 2021-01-21 09:33:45 -03:00
Torsten Grote
9bd7214d1d Make AuthorManager volatile as it is accessed from DbThread 2021-01-21 09:33:45 -03:00
Torsten Grote
fce1247aa6 Add a shortcut for setting avatar with ContactItem 2021-01-21 09:33:44 -03:00
Torsten Grote
990f983ea9 Evict Glide memory cache in a low mem situation 2021-01-21 09:33:44 -03:00
Torsten Grote
6e57d7bb42 Show avatars for contacts outside AuthorView 2021-01-21 09:33:38 -03:00
Torsten Grote
1b0cb532de Show Avatars in AuthorView 2021-01-21 09:20:03 -03:00
Torsten Grote
fe7121b4ec Turn AttachmentReader into a proper class
and inject it where needed
2021-01-21 09:20:02 -03:00
Torsten Grote
5aa041f9e1 Add AuthorManager#getMyAuthorInfo() without transaction
and add test for it
2021-01-21 09:20:02 -03:00
Torsten Grote
6939d8d230 Upgrade glide to latest stable version 2021-01-21 09:20:02 -03:00
Torsten Grote
c3cea37641 Add AttachmentHeader to AuthorInfo
This way the UI can retrieve the author's avatar (if it exists).
2021-01-21 09:20:01 -03:00
Torsten Grote
d0d2e0ed82 Centralize attachment loading in AttachmentReader
This is needed so Glide can load attachments from the DB by using the same AttachmentHeader class.
2021-01-21 09:20:01 -03:00
Torsten Grote
cf8f5c989f Move AuthorInfo from bramble to briar 2021-01-21 09:20:01 -03:00
Torsten Grote
8b45e01c42 Split up AvatarManagerImplTests 2021-01-21 09:20:00 -03:00
Torsten Grote
ec972e8a1d Handle concurrent updates of our avatar 2021-01-21 09:20:00 -03:00
Torsten Grote
100791c3f3 Don't accept incoming messages in our own avatar group 2021-01-21 09:19:59 -03:00
Torsten Grote
83ac866cc1 Implement AvatarManager with unit and integration tests 2021-01-21 09:19:59 -03:00
Torsten Grote
ef9b22670d Factor our attachment classes and constants
because they will be used by more than one client
2021-01-21 09:19:59 -03:00
Torsten Grote
186ac30f37 Use metadata constants in TransportPropertyValidator 2021-01-21 09:19:56 -03:00
Torsten Grote
5aa24414c6 Merge branch '1867-viewmodel-for-contactlistfragment' into 'master'
Introduce ViewModel for ContactListFragment

Closes #1867

See merge request briar/briar!1341
2021-01-18 13:12:43 +00:00
Sebastian Kürten
dd6d72ed30 Introduce ViewModel for ContactListFragment 2021-01-18 14:01:48 +01:00
akwizgran
4344be2ca0 Merge branch '1753-wake-lock' into 'master'
Only query for allowed packages in AndroidWakeLockManager

Closes #1753

See merge request briar/briar!1332
2021-01-12 11:21:19 +00:00
akwizgran
1e94af3ef3 Merge branch 'screenshots-api29' into 'master'
Fix screenshot instrumentation tests on API 29+

See merge request briar/briar!1333
2021-01-11 17:58:19 +00:00
Torsten Grote
cb69340749 Merge branch 'move-version-numbers-back-to-modules' into 'master'
Move version constants back into modules so F-Droid can find them

See merge request briar/briar!1338
2021-01-11 16:58:24 +00:00
akwizgran
f3d068414b Move version constants back into modules so F-Droid can find them.
This reverts commit de9c6d44, except that the version numbers have
increased in the meantime.
2021-01-11 16:46:41 +00:00
Torsten Grote
dd3c19aba2 Fix screenshot instrumentation tests on API 29+ 2021-01-05 14:40:25 -03:00
Torsten Grote
e8ede55422 Only query for allowed packages in AndroidWakeLockManager 2021-01-05 14:11:00 -03:00
404 changed files with 7519 additions and 3792 deletions

View File

@@ -2,6 +2,7 @@ image: briar/ci-image-android:latest
stages:
- test
- optional_tests
- check_reproducibility
test:
@@ -31,3 +32,33 @@ test_reproducible:
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
only:
- tags
.optional_tests:
stage: optional_tests
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
paths:
- .gradle/wrapper
- .gradle/caches
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
after_script:
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
manual_tests:
extends: .optional_tests
when: manual
except:
- tags
pre_release_tests:
extends: .optional_tests
only:
- tags

14
.idea/dictionaries/briar.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<component name="ProjectDictionaryState">
<dictionary name="briar">
<words>
<w>briar</w>
<w>briarproject</w>
<w>emoji</w>
<w>encrypter</w>
<w>identicon</w>
<w>introducee</w>
<w>introducer</w>
<w>onboarding</w>
</words>
</dictionary>
</component>

View File

@@ -13,6 +13,7 @@
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
<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-api" 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 tests in briar-headless" run_configuration_type="AndroidJUnit" />
</method>

View File

@@ -0,0 +1,14 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-api" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-api" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-api" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

24
.idea/runConfigurations/BridgeTest.xml generated Normal file
View File

@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BridgeTest" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true">
<module name="briar.bramble-java" />
<useClassPathOnly />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.briarproject.bramble.plugin.tor.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="org.briarproject.bramble.plugin.tor" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
<envs>
<env name="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
</envs>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -5,14 +5,14 @@ apply plugin: 'witness'
apply from: 'witness.gradle'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdkVersion 30
buildToolsVersion '30.0.2'
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
minSdkVersion 16
targetSdkVersion 29
versionCode 10216
versionName "1.2.16"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -38,8 +38,8 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.12@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
tor 'org.briarproject:tor-android:0.3.5.13@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.system;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.PowerManager;
@@ -106,14 +105,21 @@ class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
private String getWakeLockTag(Context ctx) {
PackageManager pm = ctx.getPackageManager();
for (PackageInfo info : pm.getInstalledPackages(0)) {
String name = info.packageName.toLowerCase();
if (name.startsWith("com.huawei.powergenie")) {
return "LocationManagerService";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
}
if (isInstalled(pm, "com.huawei.powergenie")) {
return "LocationManagerService";
} else if (isInstalled(pm, "com.evenwell.PowerMonitor")) {
return "AudioIn";
}
return ctx.getPackageName();
}
private boolean isInstalled(PackageManager pm, String packageName) {
try {
pm.getPackageInfo(packageName, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}

View File

@@ -31,6 +31,7 @@ public class AndroidUtils {
private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
private static final String STORED_REPORTS = "dev-reports";
private static final String STORED_LOGCAT = "dev-logcat";
public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>();
@@ -107,6 +108,10 @@ public class AndroidUtils {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
}
public static File getLogcatFile(Context ctx) {
return new File(ctx.getFilesDir(), STORED_LOGCAT);
}
/**
* Returns an array of supported content types for image attachments.
* GIFs can't be compressed on API < 24 so they're not supported.

View File

@@ -75,8 +75,8 @@ dependencyVerification {
'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:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.12:tor-android-0.3.5.12.zip:db71fb3290acff79d572af0752570eaf6aad7c4d88c9b9aa0b4d5afe2b9ead9c',
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
'org.briarproject:tor-android:0.3.5.13:tor-android-0.3.5.13.zip:e0978db136731dae07774b722970cdae1e462fb5adc82845dd80a7e2d87f356c',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',

View File

@@ -6,4 +6,7 @@ package org.briarproject.bramble.api;
public interface FeatureFlags {
boolean shouldEnableImageAttachments();
boolean shouldEnableProfilePictures();
}

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.db.PendingContactExistsException;
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;
@@ -179,6 +178,11 @@ public interface ContactManager {
*/
Collection<Contact> getContacts() throws DbException;
/**
* Returns all contacts.
*/
Collection<Contact> getContacts(Transaction txn) throws DbException;
/**
* Removes a contact and all associated state.
*/
@@ -215,16 +219,6 @@ 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 {
/**

View File

@@ -19,4 +19,10 @@ public interface StreamDecrypterFactory {
*/
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
SecretKey headerKey);
/**
* Creates a {@link StreamDecrypter} for decrypting a log stream.
*/
StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey);
}

View File

@@ -17,6 +17,12 @@ public interface StreamEncrypterFactory {
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
* stream.
*/
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
StreamEncrypter createContactExchangeStreamEncrypter(OutputStream out,
SecretKey headerKey);
/**
* Creates a {@link StreamEncrypter} for encrypting a log stream.
*/
StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey);
}

View File

@@ -29,4 +29,12 @@ public class NullSafety {
public static void requireNull(@Nullable Object o) {
if (o != null) throw new AssertionError();
}
/**
* Stand-in for {@code Objects.equals()}.
*/
public static boolean equals(@Nullable Object a, @Nullable Object b) {
return (a == b) || (a != null && a.equals(b));
}
}

View File

@@ -13,4 +13,6 @@ public interface DevConfig {
String getDevOnionAddress();
File getReportDir();
File getLogcatFile();
}

View File

@@ -16,8 +16,13 @@ public interface StreamReaderFactory {
/**
* Creates an {@link InputStream InputStream} for reading from a contact
* exchangestream.
* exchange stream.
*/
InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey);
/**
* Creates an {@link InputStream} for reading from a log stream.
*/
InputStream createLogStreamReader(InputStream in, SecretKey headerKey);
}

View File

@@ -7,17 +7,19 @@ import java.io.OutputStream;
@NotNullByDefault
public interface StreamWriterFactory {
/**
* Creates an {@link OutputStream OutputStream} for writing to a
* transport stream
* Creates a {@link StreamWriter} for writing to a transport stream.
*/
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
/**
* Creates an {@link OutputStream OutputStream} for writing to a contact
* exchange stream.
* Creates a {@link StreamWriter} for writing to a contact exchange stream.
*/
StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey);
/**
* Creates a {@link StreamWriter} for writing to a log stream.
*/
StreamWriter createLogStreamWriter(OutputStream out, SecretKey headerKey);
}

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.NONE;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class AuthorInfoTest extends BrambleTestCase {
@Test
public void testEquals() {
assertEquals(
new AuthorInfo(NONE),
new AuthorInfo(NONE, null)
);
assertEquals(
new AuthorInfo(NONE, "test"),
new AuthorInfo(NONE, "test")
);
assertNotEquals(
new AuthorInfo(NONE),
new AuthorInfo(VERIFIED)
);
assertNotEquals(
new AuthorInfo(NONE, "test"),
new AuthorInfo(NONE)
);
assertNotEquals(
new AuthorInfo(NONE),
new AuthorInfo(NONE, "test")
);
assertNotEquals(
new AuthorInfo(NONE, "a"),
new AuthorInfo(NONE, "b")
);
}
}

View File

@@ -20,9 +20,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
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;
@@ -40,10 +38,6 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
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
@@ -213,6 +207,11 @@ class ContactManagerImpl implements ContactManager, EventListener {
return db.transactionWithResult(true, db::getContacts);
}
@Override
public Collection<Contact> getContacts(Transaction txn) throws DbException {
return db.getContacts(txn);
}
@Override
public void removeContact(ContactId c) throws DbException {
db.transaction(false, txn -> removeContact(txn, c));
@@ -256,25 +255,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
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());
}
@Override
public void eventOccurred(Event e) {
if (e instanceof PendingContactStateChangedEvent) {

View File

@@ -9,14 +9,16 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
class ScryptKdf implements PasswordBasedKdf {
private static final Logger LOG =
Logger.getLogger(ScryptKdf.class.getName());
getLogger(ScryptKdf.class.getName());
private static final int MIN_COST = 256; // Min parameter N
private static final int MAX_COST = 1024 * 1024; // Max parameter N
@@ -33,10 +35,20 @@ class ScryptKdf implements PasswordBasedKdf {
@Override
public int chooseCostParameter() {
// Scrypt uses at least 128 * N * r bytes of memory. Don't use more
// than half of the JVM's max heap size or we may run out of memory.
// https://blog.filippo.io/the-scrypt-parameters/
long maxMemory = Runtime.getRuntime().maxMemory();
long maxCost = min(MAX_COST, maxMemory / BLOCK_SIZE / 256);
if (LOG.isLoggable(INFO) && maxCost < MAX_COST) {
LOG.info("Max cost capped at " + maxCost
+ " due to max heap size " + maxMemory);
}
// Increase the cost from min to max while measuring performance
int cost = MIN_COST;
while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS)
while (cost * 2 <= maxCost && measureDuration(cost) * 2 <= TARGET_MS) {
cost *= 2;
}
if (LOG.isLoggable(INFO))
LOG.info("KDF cost parameter " + cost);
return cost;

View File

@@ -36,4 +36,10 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
SecretKey headerKey) {
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
}
@Override
public StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey) {
return createContactExchangeStreamDecrypter(in, headerKey);
}
}

View File

@@ -51,7 +51,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
}
@Override
public StreamEncrypter createContactExchangeStreamDecrypter(
public StreamEncrypter createContactExchangeStreamEncrypter(
OutputStream out, SecretKey headerKey) {
AuthenticatedCipher cipher = cipherProvider.get();
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
@@ -60,4 +60,10 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
headerKey, frameKey);
}
@Override
public StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey) {
return createContactExchangeStreamEncrypter(out, headerKey);
}
}

View File

@@ -15,6 +15,9 @@ import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@@ -43,9 +46,9 @@ class TransportPropertyValidator extends BdfMessageValidator {
clientHelper.parseAndValidateTransportProperties(dictionary);
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("transportId", transportId);
meta.put("version", version);
meta.put("local", false);
meta.put(MSG_KEY_TRANSPORT_ID, transportId);
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, false);
return new BdfMessageContext(meta);
}
}

View File

@@ -24,15 +24,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
@Override
public InputStream createStreamReader(InputStream in, StreamContext ctx) {
return new StreamReaderImpl(
streamDecrypterFactory.createStreamDecrypter(in, ctx));
return new StreamReaderImpl(streamDecrypterFactory
.createStreamDecrypter(in, ctx));
}
@Override
public InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey) {
return new StreamReaderImpl(
streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
headerKey));
return new StreamReaderImpl(streamDecrypterFactory
.createContactExchangeStreamDecrypter(in, headerKey));
}
@Override
public InputStream createLogStreamReader(InputStream in,
SecretKey headerKey) {
return new StreamReaderImpl(streamDecrypterFactory
.createLogStreamDecrypter(in, headerKey));
}
}

View File

@@ -26,15 +26,21 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
@Override
public StreamWriter createStreamWriter(OutputStream out,
StreamContext ctx) {
return new StreamWriterImpl(
streamEncrypterFactory.createStreamEncrypter(out, ctx));
return new StreamWriterImpl(streamEncrypterFactory
.createStreamEncrypter(out, ctx));
}
@Override
public StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey) {
return new StreamWriterImpl(
streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
headerKey));
return new StreamWriterImpl(streamEncrypterFactory
.createContactExchangeStreamEncrypter(out, headerKey));
}
}
@Override
public StreamWriter createLogStreamWriter(OutputStream out,
SecretKey headerKey) {
return new StreamWriterImpl(streamEncrypterFactory
.createLogStreamEncrypter(out, headerKey));
}
}

View File

@@ -1,5 +1,14 @@
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
Bridge obfs4 52.15.78.72:9443 02069A3C5362476936B62BA6F5ACC41ABD573A9B cert=ijYG/OKc7kqu2YzKNFfeXN7/BG2BOgfEP2KyYEiGDQthnHbsOiTWHeIG0WJVW+BckzDgKw iat-mode=0
Bridge obfs4 13.58.29.242:9443 0C58939A77DA6B6B29D4B5236A75865659607AE0 cert=OylWIEHb/ezpq1zWxW0sgKRn+9ARH2eOcQOZ8/Gew+4l+oKOhQ2jUX/Y+FSl61JorXZUWA iat-mode=0
Bridge obfs4 45.33.37.112:9443 60A609BB4ABE8D46E634AE81ED29ADAB7776B399 cert=t5v19WmNv5Sc2YPNr8RQids365W7MY8zJwQVkOxBjUMFomMWARDzsbYpcWLLcw0J9Gm+BQ iat-mode=0
Bridge meek_lite 0.0.2.0:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

@@ -8,18 +8,15 @@ import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.crypto.KeyPair;
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.junit.Before;
import org.junit.Test;
@@ -31,10 +28,6 @@ import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
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.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
@@ -46,7 +39,6 @@ import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
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 {
@@ -212,75 +204,6 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
assertTrue(contactManager.contactExists(remote.getId(), local));
}
@Test
public void testGetAuthorInfo() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(singletonList(contact)));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(contact.getAlias(), authorInfo.getAlias());
}
@Test
public void testGetAuthorInfoTransaction() 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
checkAuthorInfoContext(txn, remote.getId(), singletonList(contact));
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(contact.getAlias(), authorInfo.getAlias());
// check verified contact
Contact verified = getContact(remote, local, true);
checkAuthorInfoContext(txn, remote.getId(), singletonList(verified));
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(VERIFIED, authorInfo.getStatus());
assertEquals(verified.getAlias(), authorInfo.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 checkAuthorInfoContext(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));
}});
}
@Test
public void testGetHandshakeLink() throws Exception {
Transaction txn = new Transaction(null, true);

View File

@@ -22,6 +22,17 @@ public class BrambleCoreIntegrationTestModule {
@Provides
FeatureFlags provideFeatureFlags() {
return () -> true;
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return true;
}
@Override
public boolean shouldEnableProfilePictures() {
return true;
}
};
}
}

View File

@@ -16,8 +16,8 @@ dependencies {
implementation fileTree(dir: 'libs', include: '*.jar')
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.5.12@zip'
tor 'org.briarproject:obfs4proxy:0.0.7@zip'
tor 'org.briarproject:tor:0.3.5.13@zip'
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -14,12 +14,18 @@ import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
@NotNullByDefault
class UnixTorPlugin extends JavaTorPlugin {
private static final Logger LOG = getLogger(UnixTorPlugin.class.getName());
UnixTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
NetworkManager networkManager,
@@ -41,6 +47,8 @@ class UnixTorPlugin extends JavaTorPlugin {
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory);
boolean isGlibc = isGlibc();
if (LOG.isLoggable(INFO)) LOG.info("System uses glibc: " + isGlibc);
}
@Override
@@ -48,10 +56,27 @@ class UnixTorPlugin extends JavaTorPlugin {
return CLibrary.INSTANCE.getpid();
}
protected boolean isGlibc() {
try {
GnuCLibrary glibc = Native.loadLibrary("c", GnuCLibrary.class);
if (LOG.isLoggable(INFO)) {
LOG.info("glibc version " + glibc.gnu_get_libc_version());
}
return true;
} catch (UnsatisfiedLinkError e) {
return false;
}
}
private interface CLibrary extends Library {
CLibrary INSTANCE = Native.loadLibrary("c", CLibrary.class);
int getpid();
}
private interface GnuCLibrary extends Library {
String gnu_get_libc_version();
}
}

View File

@@ -53,7 +53,7 @@ public class BridgeTest extends BrambleTestCase {
return component.getCircumventionProvider().getBridges(false);
}
private final static long TIMEOUT = SECONDS.toMillis(30);
private final static long TIMEOUT = SECONDS.toMillis(60);
private final static Logger LOG = getLogger(BridgeTest.class.getName());

View File

@@ -23,8 +23,8 @@ 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.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642',
'org.briarproject:tor:0.3.5.12:tor-0.3.5.12.zip:2f542c4befd216f2226bf7c76e3b8b2d99af6f146a8cb28bf727f42014587006',
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
'org.briarproject:tor:0.3.5.13:tor-0.3.5.13.zip:1c5f0b821ee2aadb0ea04aa96caab3ca0a08370cce8de81c2dfe04d172f8a2a0',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M16,17V14H9V10H16V7L21,12L16,17M14,2A2,2 0 0,1 16,4V6H14V4H5V20H14V18H16V20A2,2 0 0,1 14,22H5A2,2 0 0,1 3,20V4A2,2 0 0,1 5,2H14Z" /></svg>

After

Width:  |  Height:  |  Size: 423 B

View File

@@ -16,15 +16,17 @@ def getStdout = { command, defaultValue ->
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdkVersion 30
buildToolsVersion '30.0.2'
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
minSdkVersion 16
targetSdkVersion 29
versionCode 10216
versionName "1.2.16"
applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
def now = (long) (System.currentTimeMillis() / 1000)
@@ -108,7 +110,7 @@ dependencies {
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
def glideVersion = '4.10.0'
def glideVersion = '4.11.0'
implementation("com.github.bumptech.glide:glide:$glideVersion") {
exclude group: 'com.android.support'
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
@@ -134,6 +136,7 @@ dependencies {
testImplementation "org.jmock:jmock:$jmockVersion"
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
testAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
@@ -142,11 +145,9 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation 'junit:junit:4.12'
androidTestScreenshotImplementation('tools.fastlane:screengrab:1.2.0') {
// workaround for jetifier issue https://issuetracker.google.com/issues/123060356
exclude group: 'com.android.support.test.uiautomator'
}
androidTestImplementation 'junit:junit:4.13.1'
androidTestScreenshotImplementation 'tools.fastlane:screengrab:2.0.0'
androidTestScreenshotImplementation 'com.jraska:falcon:2.1.1'
androidTestScreenshotImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
import javax.inject.Singleton;
@@ -15,6 +16,7 @@ import dagger.Component;
@Component(modules = {
AppModule.class,
AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
BriarAccountModule.class,

View File

@@ -1,41 +0,0 @@
package org.briarproject.briar.android.attachment;
import android.content.res.AssetManager;
import org.junit.Before;
import java.io.IOException;
import java.io.InputStream;
import static androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
abstract class AbstractAttachmentCreationTaskTest {
private final ImageHelper imageHelper = new ImageHelperImpl();
private final ImageSizeCalculator imageSizeCalculator =
new ImageSizeCalculator(imageHelper);
private AttachmentCreationTask task;
@Before
@SuppressWarnings("ConstantConditions") // add real objects when needed
public void setUp() {
task = new AttachmentCreationTask(null,
getApplicationContext().getContentResolver(), null,
imageSizeCalculator, null, null, true);
}
void testCompress(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
task.compressImage(is, contentType);
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.briar.android.attachment;
import org.briarproject.briar.android.attachment.media.MediaModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractAttachmentRetrieverComponent {
void inject(AttachmentRetrieverIntegrationTest test);
}

View File

@@ -1,15 +1,20 @@
package org.briarproject.briar.android.attachment;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.android.attachment.media.ImageHelper;
import org.briarproject.briar.android.attachment.media.ImageSizeCalculator;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.InputStream;
import java.util.Random;
import javax.inject.Inject;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static androidx.test.InstrumentationRegistry.getContext;
@@ -24,16 +29,28 @@ public class AttachmentRetrieverIntegrationTest {
private final AttachmentDimensions dimensions = new AttachmentDimensions(
100, 50, 200, 75, 300
);
private final GroupId groupId = new GroupId(getRandomId());
private final MessageId msgId = new MessageId(getRandomId());
private final ImageHelper imageHelper = new ImageHelperImpl();
private final AttachmentRetriever retriever =
new AttachmentRetrieverImpl(null, null, dimensions, imageHelper,
new ImageSizeCalculator(imageHelper));
@Inject
ImageHelper imageHelper;
@Inject
ImageSizeCalculator imageSizeCalculator;
private final AttachmentRetriever retriever;
public AttachmentRetrieverIntegrationTest() {
AbstractAttachmentRetrieverComponent component =
DaggerAbstractAttachmentRetrieverComponent.builder().build();
component.inject(this);
retriever = new AttachmentRetrieverImpl(null, null, dimensions,
imageHelper, imageSizeCalculator);
}
@Test
public void testSmallJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("kitten_small.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -49,7 +66,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testBigJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("kitten_original.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -65,7 +82,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallPngImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/png");
InputStream is = getAssetInputStream("kitten.png");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -81,7 +98,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testUberGif() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("uber.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -96,7 +113,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testLottaPixels() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("lottapixel.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -111,7 +128,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testImageIoCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/png");
InputStream is = getAssetInputStream("image_io_crash.png");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -126,7 +143,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testGimpCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("gimp_crash.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -141,7 +158,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testOptiPngAfl() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("opti_png_afl.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -156,7 +173,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testLibrawError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("libraw_error.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -165,7 +182,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallAnimatedGifMaxDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("animated.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -180,7 +197,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallAnimatedGifHugeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("animated2.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -195,7 +212,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallGifLargeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("error_large.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -210,7 +227,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testHighError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_high.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -225,7 +242,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testWideError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_wide.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);

View File

@@ -0,0 +1,15 @@
package org.briarproject.briar.android.attachment.media;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractImageCompressorComponent {
void inject(AbstractImageCompressorTest test);
}

View File

@@ -0,0 +1,38 @@
package org.briarproject.briar.android.attachment.media;
import android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
import static androidx.test.InstrumentationRegistry.getContext;
public abstract class AbstractImageCompressorTest {
@Inject
ImageCompressor imageCompressor;
public AbstractImageCompressorTest() {
AbstractImageCompressorComponent component =
DaggerAbstractImageCompressorComponent.builder().build();
component.inject(this);
}
protected abstract void inject(
AbstractImageCompressorComponent component);
void testCompress(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
imageCompressor.compressImage(is, contentType);
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.briar.android.attachment.media;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractImageSizeCalculatorComponent {
void inject(AbstractImageSizeCalculatorTest test);
}

View File

@@ -0,0 +1,51 @@
package org.briarproject.briar.android.attachment.media;
import android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
import static androidx.test.InstrumentationRegistry.getContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public abstract class AbstractImageSizeCalculatorTest {
@Inject
ImageSizeCalculator imageSizeCalculator;
public AbstractImageSizeCalculatorTest() {
AbstractImageSizeCalculatorComponent component =
DaggerAbstractImageSizeCalculatorComponent.builder().build();
component.inject(this);
}
protected abstract void inject(
AbstractImageSizeCalculatorComponent component);
void testCanCalculateSize(String filename, String contentType, int width,
int height) throws IOException {
InputStream is = getAssetManager().open(filename);
Size size = imageSizeCalculator.getSize(is, contentType);
assertFalse(size.hasError());
assertEquals(width, size.getWidth());
assertEquals(height, size.getHeight());
}
void testCannotCalculateSize(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
Size size = imageSizeCalculator.getSize(is, contentType);
assertTrue(size.hasError());
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -9,8 +9,13 @@ import static android.os.Build.VERSION.SDK_INT;
import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class AttachmentCreationTaskTest
extends AbstractAttachmentCreationTaskTest {
public class ImageCompressorTest
extends AbstractImageCompressorTest {
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
@Test
public void testCompressSmallPng() throws Exception {

View File

@@ -0,0 +1,80 @@
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static android.os.Build.VERSION.SDK_INT;
import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class ImageSizeCalculatorTest
extends AbstractImageSizeCalculatorTest {
@Override
protected void inject(AbstractImageSizeCalculatorComponent component) {
component.inject(this);
}
@Test
public void testCalculateSizeKittenSmall() throws Exception {
testCanCalculateSize("kitten_small.jpg", "image/jpeg", 160, 240);
}
@Test
public void testCalculateSizeKittenSmallNoExif() throws Exception {
testCanCalculateSize("kitten_small_noexif.jpg", "image/jpeg", 160, 240);
}
@Test
public void testCalculateSizeSmallPng() throws Exception {
testCanCalculateSize("red-100x100.png", "image/png", 100, 100);
}
@Test
public void testCalculateSizeMediumPng() throws Exception {
testCanCalculateSize("blue-500x500.png", "image/png", 500, 500);
}
@Test
public void testCalculateSizeLargePng() throws Exception {
testCanCalculateSize("green-1000x2000.png", "image/png", 1000, 2000);
}
@Test
public void testCalculateSizeTransparentPng() throws Exception {
testCanCalculateSize("transparent-100x100.png", "image/png", 100, 100);
}
@Test
public void testCalculateSizeVeryHighJpg() throws Exception {
testCanCalculateSize("error_high.jpg", "image/jpeg", 1, 10000);
}
@Test
public void testCalculateSizeVeryWideJpg() throws Exception {
testCanCalculateSize("error_wide.jpg", "image/jpeg", 1920, 1);
}
@Test
public void testCalculateSizeAnimatedGif1() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("animated.gif", "image/gif", 65535, 65535);
}
@Test
public void testCalculateSizeAnimatedGif2() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("animated2.gif", "image/gif", 10000, 10000);
}
@Test
public void testCalculateSizeVeryLargeGif() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("error_large.gif", "image/gif", 16384, 16384);
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -17,11 +17,16 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class)
public class PngSuiteAttachmentCreationTaskTest
extends AbstractAttachmentCreationTaskTest {
public class PngSuiteImageCompressorTest
extends AbstractImageCompressorTest {
private static final Logger LOG =
getLogger(PngSuiteAttachmentCreationTaskTest.class.getName());
getLogger(PngSuiteImageCompressorTest.class.getName());
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
@Parameters
public static Iterable<String> data() throws IOException {
@@ -34,14 +39,14 @@ public class PngSuiteAttachmentCreationTaskTest
private final String filename;
public PngSuiteAttachmentCreationTaskTest(String filename) {
public PngSuiteImageCompressorTest(String filename) {
this.filename = filename;
}
@Test
public void testPngSuiteCompress() throws Exception {
assumeTrue(isOptionalTestEnabled(
PngSuiteAttachmentCreationTaskTest.class));
PngSuiteImageCompressorTest.class));
LOG.info("Testing " + filename);
if (filename.startsWith("x")) {
try {

View File

@@ -0,0 +1,80 @@
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class)
public class PngSuiteImageSizeCalculatorTest
extends AbstractImageSizeCalculatorTest {
private static final Logger LOG =
getLogger(PngSuiteImageSizeCalculatorTest.class.getName());
@Override
protected void inject(AbstractImageSizeCalculatorComponent component) {
component.inject(this);
}
@Parameters
public static Iterable<String> data() throws IOException {
List<String> data = new ArrayList<>();
String[] files = requireNonNull(getAssetManager().list("PngSuite"));
for (String filename : files)
if (filename.endsWith(".png")) data.add(filename);
return data;
}
private final String filename;
public PngSuiteImageSizeCalculatorTest(String filename) {
this.filename = filename;
}
// some files have sizes other than 32x32
private Map<String, Size> customSizes = new HashMap<>();
{
customSizes.put("cdfn2c08.png", new Size(8, 32, "image/png"));
customSizes.put("cdhn2c08.png", new Size(32, 8, "image/png"));
customSizes.put("cdsn2c08.png", new Size(8, 8, "image/png"));
customSizes.put("PngSuite.png", new Size(256, 256, "image/png"));
}
@Test
public void testPngSuiteCalculateSizes() throws Exception {
assumeTrue(isOptionalTestEnabled(
PngSuiteImageSizeCalculatorTest.class));
LOG.info("Testing " + filename);
if (filename.startsWith("x") && !filename.equals("xcsn0g01.png")) {
testCannotCalculateSize("PngSuite/" + filename, "image/png");
} else if (filename.startsWith("s")) {
int size = Integer.parseInt(filename.substring(1, 3));
testCanCalculateSize("PngSuite/" + filename, "image/png", size,
size);
} else {
int width = 32;
int height = 32;
if (customSizes.containsKey(filename)) {
Size size = customSizes.get(filename);
width = size.getWidth();
height = size.getHeight();
}
testCanCalculateSize("PngSuite/" + filename, "image/png", width,
height);
}
}
}

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.ConversationActivityScreenshotTest;
import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest;
@@ -16,6 +17,7 @@ import dagger.Component;
@Component(modules = {
AppModule.class,
AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
BriarAccountModule.class,
@@ -26,6 +28,7 @@ public interface BriarUiTestComponent extends AndroidComponent {
void inject(SetupDataTest test);
void inject(ConversationActivityScreenshotTest test);
void inject(SettingsActivityScreenshotTest test);
}

View File

@@ -116,7 +116,7 @@ public class SetupDataTest extends ScreenshotTest {
throws DbException {
Context ctx = getApplicationContext();
String bobName = ctx.getString(R.string.screenshot_bob);
Contact bob = testDataCreator.addContact(bobName);
Contact bob = testDataCreator.addContact(bobName, true);
// TODO add messages

View File

@@ -0,0 +1,105 @@
/*
* Some code was taken from:
*
* RIG Random Image Generator
* https://github.com/stedi-akk/RandomImageGenerator
* licenced under Apache2 license.
*/
package org.briarproject.briar.android.test;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import javax.annotation.Nullable;
import javax.inject.Inject;
public class TestAvatarCreatorImpl implements TestAvatarCreator {
private final int WIDTH = 800;
private final int HEIGHT = 640;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final float[] hsv = new float[3];
private final Random random = new Random();
private final ImageCompressor imageCompressor;
@Inject
TestAvatarCreatorImpl(ImageCompressor imageCompressor) {
this.imageCompressor = imageCompressor;
}
@Nullable
@Override
public InputStream getAvatarInputStream() throws IOException {
Bitmap bitmap = generateBitmap();
return imageCompressor.compressImage(bitmap);
}
private Bitmap generateBitmap() {
// one pattern is boring, let's at least use two
if (random.nextBoolean()) {
return generateColoredPixels();
} else {
return generateColoredCircles();
}
}
private Bitmap generateColoredPixels() {
Bitmap bitmap = getBitmapWithRandomBackground();
Canvas canvas = new Canvas(bitmap);
Rect pixel = new Rect();
int pixelMultiplier = random.nextInt(500) + 1;
for (int x = 0; x < WIDTH; x += pixelMultiplier) {
for (int y = 0; y < HEIGHT; y += pixelMultiplier) {
pixel.set(x, y, x + pixelMultiplier, y + pixelMultiplier);
paint.setColor(getRandomColor());
canvas.drawRect(pixel, paint);
}
}
return bitmap;
}
private Bitmap generateColoredCircles() {
Bitmap bitmap = getBitmapWithRandomBackground();
int biggestSide = Math.max(WIDTH, HEIGHT);
int selectedCount = random.nextInt(10) + 2;
Canvas canvas = new Canvas(bitmap);
float radiusFrom = biggestSide / 12f;
float radiusTo = biggestSide / 4f;
for (int i = 0; i < selectedCount; i++) {
float cx = random.nextInt(WIDTH);
float cy = random.nextInt(HEIGHT);
float radius =
random.nextInt((int) (radiusTo - radiusFrom)) + radiusFrom;
paint.setColor(getRandomColor());
canvas.drawCircle(cx, cy, radius, paint);
}
return bitmap;
}
private Bitmap getBitmapWithRandomBackground() {
Bitmap bitmap =
Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(getRandomColor());
return bitmap;
}
private int getRandomColor() {
hsv[0] = random.nextInt(360);
hsv[1] = random.nextFloat();
hsv[2] = 1f;
return Color.HSVToColor(hsv);
}
}

View File

@@ -453,6 +453,8 @@
<queries>
<package android:name="info.guardianproject.ripple" />
<package android:name="com.huawei.systemmanager" />
<package android:name="com.huawei.powergenie" />
<package android:name="com.evenwell.PowerMonitor" />
<intent>
<action android:name="android.intent.action.VIEW" />

View File

@@ -32,13 +32,16 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.view.EmojiTextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.attachment.AttachmentReader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -47,6 +50,7 @@ import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -71,7 +75,8 @@ import dagger.Component;
BrambleAndroidModule.class,
BriarAccountModule.class,
AppModule.class,
AttachmentModule.class
AttachmentModule.class,
MediaModule.class
})
public interface AndroidComponent
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
@@ -94,6 +99,10 @@ public interface AndroidComponent
IdentityManager identityManager();
AttachmentReader attachmentReader();
AuthorManager authorManager();
PluginManager pluginManager();
EventBus eventBus();
@@ -171,6 +180,10 @@ public interface AndroidComponent
AndroidWakeLockManager wakeLockManager();
CachingLogHandler logHandler();
Thread.UncaughtExceptionHandler exceptionHandler();
void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService);

View File

@@ -255,7 +255,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
R.string.ongoing_notification_title;
int text = locked ? R.string.lock_tap_to_unlock :
R.string.ongoing_notification_text;
int icon = locked ? R.drawable.startup_lock :
int icon = locked ? R.drawable.notification_lock :
R.drawable.notification_ongoing;
// Ongoing foreground notification that shows BriarService is running
NotificationCompat.Builder b =
@@ -624,7 +624,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext, REMINDER_CHANNEL_ID);
b.setSmallIcon(R.drawable.ic_signout);
b.setSmallIcon(R.drawable.notification_signout);
b.setColor(getColor(appContext, R.color.briar_primary));
b.setContentTitle(
appContext.getText(R.string.reminder_notification_title));

View File

@@ -27,18 +27,27 @@ import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.DozeHelperModule;
import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.account.SetupModule;
import org.briarproject.briar.android.contact.ContactListModule;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.logging.LoggingModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.reporting.DevReportModule;
import org.briarproject.briar.android.settings.SettingsModule;
import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.File;
import java.security.GeneralSecurityException;
@@ -63,14 +72,21 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBL
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = {
SetupModule.class,
DozeHelperModule.class,
ContactExchangeModule.class,
LoggingModule.class,
LoginModule.class,
NavDrawerModule.class,
ViewModelModule.class,
SettingsModule.class,
DevReportModule.class,
ContactListModule.class,
// below need to be within same scope as ViewModelProvider.Factory
ForumModule.BindsModule.class,
ForumModule.class,
GroupListModule.class,
GroupConversationModule.class,
SharingModule.class,
})
public class AppModule {
@@ -178,10 +194,21 @@ public class AppModule {
public File getReportDir() {
return AndroidUtils.getReportDir(app.getApplicationContext());
}
@Override
public File getLogcatFile() {
return AndroidUtils.getLogcatFile(app.getApplicationContext());
}
};
return devConfig;
}
@Provides
TestAvatarCreator provideTestAvatarCreator(
TestAvatarCreatorImpl testAvatarCreator) {
return testAvatarCreator;
}
@Provides
SharedPreferences provideSharedPreferences(Application app) {
// FIXME unify this with getDefaultSharedPreferences()
@@ -245,6 +272,17 @@ public class AppModule {
@Provides
FeatureFlags provideFeatureFlags() {
return () -> IS_DEBUG_BUILD;
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return IS_DEBUG_BUILD;
}
@Override
public boolean shouldEnableProfilePictures() {
return IS_DEBUG_BUILD;
}
};
}
}

View File

@@ -6,9 +6,6 @@ import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleApplication;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import java.util.Collection;
import java.util.logging.LogRecord;
/**
* This exists so that the Application object will not necessarily be cast
* directly to the Briar application object.
@@ -17,8 +14,6 @@ public interface BriarApplication extends BrambleApplication {
Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class;
Collection<LogRecord> getRecentLogRecords();
AndroidComponent getApplicationComponent();
SharedPreferences getDefaultSharedPreferences();

View File

@@ -20,12 +20,10 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.reporting.BriarExceptionHandler;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Collection;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import androidx.annotation.NonNull;
@@ -42,22 +40,18 @@ public class BriarApplicationImpl extends Application
private static final Logger LOG =
getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler();
private final BriarExceptionHandler exceptionHandler =
new BriarExceptionHandler(this);
private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs;
@Override
protected void attachBaseContext(Context base) {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
if (prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here.
Localizer.initialize(prefs);
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
setTheme(base, prefs);
}
@@ -67,6 +61,11 @@ public class BriarApplicationImpl extends Application
if (IS_DEBUG_BUILD) enableStrictMode();
applicationComponent = createApplicationComponent();
UncaughtExceptionHandler exceptionHandler =
applicationComponent.exceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
Logger rootLogger = getLogger("");
Handler[] handlers = rootLogger.getHandlers();
// Disable the Android logger for release builds
@@ -78,12 +77,12 @@ public class BriarApplicationImpl extends Application
// Restore the default handlers after the level raising handler
for (Handler handler : handlers) rootLogger.addHandler(handler);
}
CachingLogHandler logHandler = applicationComponent.logHandler();
rootLogger.addHandler(logHandler);
rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO);
LOG.info("Created");
applicationComponent = createApplicationComponent();
EmojiManager.install(new GoogleEmojiProvider());
}
@@ -136,11 +135,6 @@ public class BriarApplicationImpl extends Application
return applicationComponent;
}
@Override
public Collection<LogRecord> getRecentLogRecords() {
return logHandler.getRecentLogRecords();
}
@Override
public AndroidComponent getApplicationComponent() {
return applicationComponent;

View File

@@ -14,6 +14,8 @@ import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import com.bumptech.glide.Glide;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -35,7 +37,7 @@ import javax.inject.Inject;
import androidx.core.app.NotificationCompat;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
@@ -54,6 +56,7 @@ import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK;
@@ -118,11 +121,17 @@ public class BriarService extends Service {
if (SDK_INT >= 26) {
NotificationManager nm = (NotificationManager)
requireNonNull(getSystemService(NOTIFICATION_SERVICE));
// Delete the old notification channel, which had
// IMPORTANCE_NONE and showed a badge
nm.deleteNotificationChannel(ONGOING_CHANNEL_OLD_ID);
// Use IMPORTANCE_LOW so the system doesn't show its own
// notification on API 26-27
NotificationChannel ongoingChannel = new NotificationChannel(
ONGOING_CHANNEL_ID,
getString(R.string.ongoing_notification_title),
IMPORTANCE_NONE);
IMPORTANCE_LOW);
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
ongoingChannel.setShowBadge(false);
nm.createNotificationChannel(ongoingChannel);
NotificationChannel failureChannel = new NotificationChannel(
FAILURE_CHANNEL_ID,
@@ -168,6 +177,7 @@ public class BriarService extends Service {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
}
private void showStartupFailureNotification(StartResult result) {
@@ -246,10 +256,13 @@ public class BriarService extends Service {
LOG.info("Trim memory: near end of LRU list");
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) {
LOG.info("Trim memory: running moderately low");
Glide.get(getApplicationContext()).clearMemory();
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
LOG.info("Trim memory: running low");
// TODO investigate if we can clear Glide cache here as well
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
LOG.warning("Trim memory: running critically low");
// TODO investigate if we can clear Glide cache here as well
// If we're not in the foreground, clear the UI to save memory
if (app.isRunningInBackground()) hideUi();
} else if (LOG.isLoggable(INFO)) {

View File

@@ -68,7 +68,21 @@ public class Localizer {
return new Locale(tag);
}
// Returns the localized version of context
/*
* Apply localization to the specified context.
*
* It updates the configuration of the context's resources object but can
* also return a new context derived from the context parameter. Hence
* make sure to work with the return value of this method instead of
* the context you passed as a parameter.
*
* This method also has side-effects as it calls Locale#setDefault().
*
* When using this in attachBaseContext() of Application, Service or
* Activity subclasses, it is important to not only apply this method to the
* base Context parameter received in that method, but also apply it on the
* class itself which also extends Context.
*/
public Context setLocale(Context context) {
Resources res = context.getResources();
Configuration conf = res.getConfiguration();
@@ -82,7 +96,7 @@ public class Localizer {
Locale.setDefault(locale);
if (SDK_INT >= 17) {
conf.setLocale(locale);
context.createConfigurationContext(conf);
context = context.createConfigurationContext(conf);
} else
conf.locale = locale;
//noinspection deprecation

View File

@@ -81,8 +81,7 @@ public class AuthorNameFragment extends SetupFragment {
public void onClick(View view) {
Editable text = authorNameInput.getText();
if (text != null) {
setupController.setAuthorName(text.toString().trim());
setupController.showPasswordFragment();
viewModel.setAuthorName(text.toString().trim());
}
}

View File

@@ -104,9 +104,15 @@ public class DozeFragment extends SetupFragment
@Override
public void onClick(View view) {
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
setNextClicked();
viewModel.dozeExceptionConfirmed();
}
@Override
void setNextClicked() {
super.setNextClicked();
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
}

View File

@@ -0,0 +1,8 @@
package org.briarproject.briar.android.account;
import android.content.Context;
interface DozeHelper {
boolean needToShowDozeFragment(Context context);
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import static org.briarproject.briar.android.account.HuaweiView.needsToBeShown;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
class DozeHelperImpl implements DozeHelper {
@Override
public boolean needToShowDozeFragment(Context context) {
return needsDozeWhitelisting(context.getApplicationContext()) ||
needsToBeShown(context.getApplicationContext());
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.briar.android.account;
import dagger.Module;
import dagger.Provides;
@Module
public class DozeHelperModule {
@Provides
DozeHelper provideDozeHelper() {
return new DozeHelperImpl();
}
}

View File

@@ -30,7 +30,6 @@ import static org.briarproject.briar.android.util.UiUtils.setError;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SetPasswordFragment extends SetupFragment {
private final static String TAG = SetPasswordFragment.class.getName();
private TextInputLayout passwordEntryWrapper;
@@ -56,7 +55,7 @@ public class SetPasswordFragment extends SetupFragment {
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container,
false);
false);
strengthMeter = v.findViewById(R.id.strength_meter);
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
@@ -71,7 +70,7 @@ public class SetPasswordFragment extends SetupFragment {
passwordConfirmation.addTextChangedListener(this);
nextButton.setOnClickListener(this);
if (!setupController.needToShowDozeFragment()) {
if (!viewModel.needToShowDozeFragment()) {
nextButton.setText(R.string.create_account_button);
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
}
@@ -97,7 +96,7 @@ public class SetPasswordFragment extends SetupFragment {
strengthMeter
.setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE);
float strength = setupController.estimatePasswordStrength(password1);
float strength = viewModel.estimatePasswordStrength(password1);
strengthMeter.setStrength(strength);
boolean strongEnough = strength >= QUITE_WEAK;
@@ -117,14 +116,20 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
setupController.setPassword(passwordEntry.getText().toString());
if (setupController.needToShowDozeFragment()) {
setupController.showDozeFragment();
} else {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
}
setNextClicked();
viewModel.setPassword(passwordEntry.getText().toString());
}
@Override
void setNextClicked() {
super.setNextClicked();
passwordEntry.setFocusable(false);
passwordConfirmation.setFocusable(false);
if (!viewModel.needToShowDozeFragment()) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
}
}

View File

@@ -4,7 +4,6 @@ import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Bundle;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
@@ -15,28 +14,36 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED;
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SetupActivity extends BaseActivity
implements BaseFragmentListener {
private static final String STATE_KEY_AUTHOR_NAME = "authorName";
private static final String STATE_KEY_PASSWORD = "password";
@Inject
AccountManager accountManager;
ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel;
@Inject
SetupController setupController;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
@Nullable
private String authorName, password;
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(SetupViewModel.class);
viewModel.getState().observeEvent(this, this::onStateChanged);
}
@Override
public void onCreate(@Nullable Bundle state) {
@@ -44,58 +51,27 @@ public class SetupActivity extends BaseActivity
// fade-in after splash screen instead of default animation
overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
setContentView(R.layout.activity_fragment_container);
}
if (state == null) {
if (accountManager.accountExists()) throw new AssertionError();
private void onStateChanged(SetupViewModel.State state) {
if (state == AUTHOR_NAME) {
showInitialFragment(AuthorNameFragment.newInstance());
} else {
authorName = state.getString(STATE_KEY_AUTHOR_NAME);
password = state.getString(STATE_KEY_PASSWORD);
} else if (state == SET_PASSWORD) {
showPasswordFragment();
} else if (state == DOZE) {
showDozeFragment();
} else if (state == CREATED || state == FAILED) {
// TODO: Show an error if failed
showApp();
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
setupController.setSetupActivity(this);
}
@Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
if (authorName != null)
state.putString(STATE_KEY_AUTHOR_NAME, authorName);
if (password != null)
state.putString(STATE_KEY_PASSWORD, password);
}
@Nullable
String getAuthorName() {
return authorName;
}
void setAuthorName(String authorName) {
this.authorName = authorName;
}
@Nullable
String getPassword() {
return password;
}
void setPassword(String password) {
this.password = password;
}
void showPasswordFragment() {
if (authorName == null) throw new IllegalStateException();
showNextFragment(SetPasswordFragment.newInstance());
}
@TargetApi(23)
void showDozeFragment() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
showNextFragment(DozeFragment.newInstance());
}

View File

@@ -1,34 +0,0 @@
package org.briarproject.briar.android.account;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface SetupController {
void setSetupActivity(SetupActivity setupActivity);
boolean needToShowDozeFragment();
void setAuthorName(String authorName);
float estimatePasswordStrength(String password);
void setPassword(String password);
/**
* This should be called after the author name has been set.
*/
void showPasswordFragment();
/**
* This should be called after the author name and the password have been
* set.
*/
void showDozeFragment();
/**
* This should be called after the author name and the password have been
* set.
*/
void createAccount();
}

View File

@@ -1,115 +0,0 @@
package org.briarproject.briar.android.account;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
@NotNullByDefault
public class SetupControllerImpl implements SetupController {
private static final Logger LOG =
Logger.getLogger(SetupControllerImpl.class.getName());
private final AccountManager accountManager;
private final PasswordStrengthEstimator strengthEstimator;
@IoExecutor
private final Executor ioExecutor;
@Nullable
private volatile SetupActivity setupActivity;
@Inject
SetupControllerImpl(AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator) {
this.accountManager = accountManager;
this.strengthEstimator = strengthEstimator;
this.ioExecutor = ioExecutor;
}
@Override
public void setSetupActivity(SetupActivity setupActivity) {
this.setupActivity = setupActivity;
}
@Override
public boolean needToShowDozeFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
return DozeView.needsToBeShown(setupActivity) ||
HuaweiView.needsToBeShown(setupActivity);
}
@Override
public void setAuthorName(String authorName) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.setAuthorName(authorName);
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
@Override
public void setPassword(String password) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.setPassword(password);
}
@Override
public void showPasswordFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.showPasswordFragment();
}
@Override
public void showDozeFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.showDozeFragment();
}
@Override
public void createAccount() {
SetupActivity setupActivity = this.setupActivity;
UiResultHandler<Boolean> resultHandler =
new UiResultHandler<Boolean>(setupActivity) {
@Override
public void onResultUi(Boolean result) {
// TODO: Show an error if result is false
if (setupActivity == null)
throw new IllegalStateException();
setupActivity.showApp();
}
};
createAccount(resultHandler);
}
// Package access for testing
void createAccount(ResultHandler<Boolean> resultHandler) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
String authorName = setupActivity.getAuthorName();
if (authorName == null) throw new IllegalStateException();
String password = setupActivity.getPassword();
if (password == null) throw new IllegalStateException();
ioExecutor.execute(() -> {
LOG.info("Creating account");
resultHandler.onResult(accountManager.createAccount(authorName,
password));
});
}
}

View File

@@ -1,11 +1,13 @@
package org.briarproject.briar.android.account;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -17,7 +19,10 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
@@ -29,8 +34,40 @@ import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener {
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
private boolean clicked = false;
@Inject
SetupController setupController;
ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(requireActivity())
.get(SetupViewModel.class);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
}
if (clicked) {
setNextClicked();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_KEY_CLICKED, clicked);
}
@CallSuper
void setNextClicked() {
this.clicked = true;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

View File

@@ -0,0 +1,18 @@
package org.briarproject.briar.android.account;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class SetupModule {
@Binds
@IntoMap
@ViewModelKey(SetupViewModel.class)
abstract ViewModel bindSetupViewModel(
SetupViewModel setupViewModel);
}

View File

@@ -0,0 +1,110 @@
package org.briarproject.briar.android.account;
import android.app.Application;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED;
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class SetupViewModel extends AndroidViewModel {
enum State {AUTHOR_NAME, SET_PASSWORD, DOZE, CREATED, FAILED}
private static final Logger LOG =
getLogger(SetupActivity.class.getName());
@Nullable
private String authorName, password;
private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
private final AccountManager accountManager;
private final Executor ioExecutor;
private final PasswordStrengthEstimator strengthEstimator;
private final DozeHelper dozeHelper;
@Inject
SetupViewModel(Application app,
AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator,
DozeHelper dozeHelper) {
super(app);
this.accountManager = accountManager;
this.ioExecutor = ioExecutor;
this.strengthEstimator = strengthEstimator;
this.dozeHelper = dozeHelper;
ioExecutor.execute(() -> {
if (accountManager.accountExists()) {
throw new AssertionError();
} else {
state.postEvent(AUTHOR_NAME);
}
});
}
LiveEvent<State> getState() {
return state;
}
void setAuthorName(String authorName) {
this.authorName = authorName;
state.setEvent(SET_PASSWORD);
}
void setPassword(String password) {
if (authorName == null) throw new IllegalStateException();
this.password = password;
if (needToShowDozeFragment()) {
state.setEvent(DOZE);
} else {
createAccount();
}
}
float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
boolean needToShowDozeFragment() {
return dozeHelper.needToShowDozeFragment(getApplication());
}
void dozeExceptionConfirmed() {
createAccount();
}
private void createAccount() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
ioExecutor.execute(() -> {
if (accountManager.createAccount(authorName, password)) {
LOG.info("Created account");
state.postEvent(CREATED);
} else {
LOG.warning("Failed to create account");
state.postEvent(FAILED);
}
});
}
}

View File

@@ -21,7 +21,6 @@ import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
@@ -33,7 +32,6 @@ import org.briarproject.briar.android.conversation.ImageFragment;
import org.briarproject.briar.android.forum.CreateForumActivity;
import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.forum.ForumListFragment;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.introduction.ContactChooserFragment;
import org.briarproject.briar.android.introduction.IntroductionActivity;
@@ -51,7 +49,6 @@ import org.briarproject.briar.android.navdrawer.TransportsActivity;
import org.briarproject.briar.android.panic.PanicPreferencesActivity;
import org.briarproject.briar.android.panic.PanicResponderActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
import org.briarproject.briar.android.privategroup.creation.CreateGroupModule;
@@ -68,6 +65,7 @@ import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment
import org.briarproject.briar.android.reporting.CrashFragment;
import org.briarproject.briar.android.reporting.CrashReportActivity;
import org.briarproject.briar.android.reporting.ReportFormFragment;
import org.briarproject.briar.android.settings.ConfirmAvatarDialogFragment;
import org.briarproject.briar.android.settings.SettingsActivity;
import org.briarproject.briar.android.settings.SettingsFragment;
import org.briarproject.briar.android.sharing.BlogInvitationActivity;
@@ -88,14 +86,11 @@ import dagger.Component;
@Component(modules = {
ActivityModule.class,
BlogModule.class,
ContactModule.class,
CreateGroupModule.class,
ForumModule.class,
GroupInvitationModule.class,
GroupConversationModule.class,
GroupMemberModule.class,
GroupRevealModule.class,
SharingModule.class
SharingModule.SharingLegacyModule.class
}, dependencies = AndroidComponent.class)
public interface ActivityComponent {
@@ -241,4 +236,6 @@ public interface ActivityComponent {
void inject(CrashFragment crashFragment);
void inject(ConfirmAvatarDialogFragment fragment);
}

View File

@@ -2,8 +2,6 @@ package org.briarproject.briar.android.activity;
import android.app.Activity;
import org.briarproject.briar.android.account.SetupController;
import org.briarproject.briar.android.account.SetupControllerImpl;
import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl;
import org.briarproject.briar.android.controller.DbController;
@@ -35,13 +33,6 @@ public class ActivityModule {
return activity;
}
@ActivityScope
@Provides
SetupController provideSetupController(
SetupControllerImpl setupController) {
return setupController;
}
@ActivityScope
@Provides
protected BriarController provideBriarController(

View File

@@ -14,7 +14,6 @@ import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.Localizer;
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.util.UiUtils;
@@ -87,7 +86,6 @@ public abstract class BaseActivity extends AppCompatActivity
activityComponent = DaggerActivityComponent.builder()
.androidComponent(applicationComponent)
.activityModule(getActivityModule())
.forumModule(getForumModule())
.build();
injectActivity(activityComponent);
super.onCreate(state);
@@ -111,6 +109,7 @@ public abstract class BaseActivity extends AppCompatActivity
protected void attachBaseContext(Context base) {
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
}
public ActivityComponent getActivityComponent() {
@@ -122,11 +121,6 @@ public abstract class BaseActivity extends AppCompatActivity
return new ActivityModule(this);
}
// TODO use a test module where this is used in tests
protected ForumModule getForumModule() {
return new ForumModule();
}
@Override
protected void onStart() {
super.onStart();

View File

@@ -160,7 +160,6 @@ public abstract class BriarActivity extends BaseActivity {
* @param ownLayout true if the custom toolbar brings its own layout
* @return the Toolbar object or null if content view did not contain one
*/
@Nullable
protected Toolbar setUpCustomToolbar(boolean ownLayout) {
// Custom Toolbar
Toolbar toolbar = findViewById(R.id.toolbar);

View File

@@ -16,5 +16,6 @@ public interface RequestCodes {
int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13;
int REQUEST_SAVE_ATTACHMENT = 14;
int REQUEST_AVATAR_IMAGE = 15;
}

View File

@@ -1,33 +1,24 @@
package org.briarproject.briar.android.attachment;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
import android.net.Uri;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.logging.Logger;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import static android.graphics.Bitmap.CompressFormat.JPEG;
import static android.graphics.BitmapFactory.decodeStream;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
@@ -35,19 +26,16 @@ import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault
class AttachmentCreationTask {
private static Logger LOG =
private static final Logger LOG =
getLogger(AttachmentCreationTask.class.getName());
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
private final MessagingManager messagingManager;
private final ContentResolver contentResolver;
private final ImageSizeCalculator imageSizeCalculator;
private final ImageCompressor imageCompressor;
private final GroupId groupId;
private final Collection<Uri> uris;
private final boolean needsSize;
@@ -59,11 +47,11 @@ class AttachmentCreationTask {
AttachmentCreationTask(MessagingManager messagingManager,
ContentResolver contentResolver,
AttachmentCreator attachmentCreator,
ImageSizeCalculator imageSizeCalculator,
ImageCompressor imageCompressor,
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
this.messagingManager = messagingManager;
this.contentResolver = contentResolver;
this.imageSizeCalculator = imageSizeCalculator;
this.imageCompressor = imageCompressor;
this.groupId = groupId;
this.uris = uris;
this.needsSize = needsSize;
@@ -110,66 +98,19 @@ class AttachmentCreationTask {
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
if (!asList(getSupportedImageContentTypes()).contains(contentType)) {
String uriString = uri.toString();
throw new UnsupportedMimeTypeException("", contentType, uriString);
throw new UnsupportedMimeTypeException(contentType, uri);
}
InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
is = compressImage(is, contentType);
contentType = "image/jpeg";
is = imageCompressor
.compressImage(is, contentType);
long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is);
.addLocalAttachment(groupId, timestamp,
ImageCompressor.MIME_TYPE, is);
tryToClose(is, LOG, WARNING);
logDuration(LOG, "Storing attachment", start);
return h;
}
@VisibleForTesting
InputStream compressImage(InputStream is, String contentType)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
Bitmap bitmap = createBitmap(is, contentType);
for (int quality = 100; quality >= 0; quality -= 10) {
if (!bitmap.compress(JPEG, quality, out))
throw new IOException();
if (out.size() <= MAX_IMAGE_SIZE) {
if (LOG.isLoggable(INFO)) {
LOG.info("Compressed image to "
+ out.size() + " bytes, quality " + quality);
}
return new ByteArrayInputStream(out.toByteArray());
}
out.reset();
}
throw new IOException();
} finally {
tryToClose(is, LOG, WARNING);
}
}
private Bitmap createBitmap(InputStream is, String contentType)
throws IOException {
is = new BufferedInputStream(is);
Size size = imageSizeCalculator.getSize(is, contentType);
if (size.error) throw new IOException();
if (LOG.isLoggable(INFO))
LOG.info("Original image size: " + size.width + "x" + size.height);
int dimension = Math.max(size.width, size.height);
int inSampleSize = 1;
while (dimension > MAX_ATTACHMENT_DIMENSION) {
inSampleSize *= 2;
dimension /= 2;
}
if (LOG.isLoggable(INFO))
LOG.info("Scaling attachment by factor of " + inSampleSize);
Options options = new Options();
options.inSampleSize = inSampleSize;
if (contentType.equals("image/png"))
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = decodeStream(is, null, options);
if (bitmap == null) throw new IOException();
return bitmap;
}
}

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.Collection;
import java.util.List;

View File

@@ -10,11 +10,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.FileTooBigException;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException;
import java.util.ArrayList;
@@ -36,12 +36,12 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
import static org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE;
@NotNullByDefault
class AttachmentCreatorImpl implements AttachmentCreator {
private static Logger LOG =
private static final Logger LOG =
getLogger(AttachmentCreatorImpl.class.getName());
private final Application app;
@@ -49,7 +49,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
private final Executor ioExecutor;
private final MessagingManager messagingManager;
private final AttachmentRetriever retriever;
private final ImageSizeCalculator imageSizeCalculator;
private final ImageCompressor imageCompressor;
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
@@ -64,12 +64,12 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@Inject
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
MessagingManager messagingManager, AttachmentRetriever retriever,
ImageSizeCalculator imageSizeCalculator) {
ImageCompressor imageCompressor) {
this.app = app;
this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager;
this.retriever = retriever;
this.imageSizeCalculator = imageSizeCalculator;
this.imageCompressor = imageCompressor;
}
@Override
@@ -89,7 +89,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
if (id == null) throw new IllegalStateException();
boolean needsSize = uris.size() == 1;
task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, imageSizeCalculator, id,
app.getContentResolver(), this, imageCompressor, id,
uris, needsSize);
ioExecutor.execute(() -> task.storeAttachments());
});

View File

@@ -4,8 +4,9 @@ import android.os.Parcel;
import android.os.Parcelable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import javax.annotation.concurrent.Immutable;
@@ -78,6 +79,9 @@ public class AttachmentItem implements Parcelable {
}
protected AttachmentItem(Parcel in) {
byte[] groupIdByte = new byte[GroupId.LENGTH];
in.readByteArray(groupIdByte);
GroupId groupId = new GroupId(groupIdByte);
byte[] messageIdByte = new byte[MessageId.LENGTH];
in.readByteArray(messageIdByte);
MessageId messageId = new MessageId(messageIdByte);
@@ -88,7 +92,7 @@ public class AttachmentItem implements Parcelable {
thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt();
state = State.valueOf(requireNonNull(in.readString()));
header = new AttachmentHeader(messageId, mimeType);
header = new AttachmentHeader(groupId, messageId, mimeType);
}
public AttachmentHeader getHeader() {
@@ -142,6 +146,7 @@ public class AttachmentItem implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(header.getGroupId().getBytes());
dest.writeByteArray(header.getMessageId().getBytes());
dest.writeInt(width);
dest.writeInt(height);

View File

@@ -3,7 +3,7 @@ package org.briarproject.briar.android.attachment;
import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.Collection;
import java.util.List;

View File

@@ -12,16 +12,6 @@ import static org.briarproject.briar.android.attachment.AttachmentDimensions.get
@Module
public class AttachmentModule {
@Provides
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
return imageHelper;
}
@Provides
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
return new ImageSizeCalculator(imageHelper);
}
@Provides
AttachmentDimensions provideAttachmentDimensions(Application app) {
return getAttachmentDimensions(app.getResources());

View File

@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
@@ -49,10 +49,10 @@ public interface AttachmentRetriever {
* Loads an {@link AttachmentItem}
* that arrived via an {@link AttachmentReceivedEvent}
* and notifies the associated {@link LiveData}.
*
* <p>
* Note that you need to call {@link #getAttachmentItems(PrivateMessageHeader)}
* first to get the LiveData.
*
* <p>
* It is possible that no LiveData is available,
* because the message of the AttachmentItem did not arrive, yet.
* In this case, the load wil be deferred until the message arrives.

View File

@@ -6,9 +6,12 @@ import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem.State;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.android.attachment.media.ImageHelper;
import org.briarproject.briar.android.attachment.media.ImageSizeCalculator;
import org.briarproject.briar.android.attachment.media.Size;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentReader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.BufferedInputStream;
@@ -43,7 +46,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor
private final Executor dbExecutor;
private final MessagingManager messagingManager;
private final AttachmentReader attachmentReader;
private final ImageHelper imageHelper;
private final ImageSizeCalculator imageSizeCalculator;
private final int defaultSize;
@@ -57,11 +60,10 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@Inject
AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor,
MessagingManager messagingManager,
AttachmentDimensions dimensions, ImageHelper imageHelper,
ImageSizeCalculator imageSizeCalculator) {
AttachmentReader attachmentReader, AttachmentDimensions dimensions,
ImageHelper imageHelper, ImageSizeCalculator imageSizeCalculator) {
this.dbExecutor = dbExecutor;
this.messagingManager = messagingManager;
this.attachmentReader = attachmentReader;
this.imageHelper = imageHelper;
this.imageSizeCalculator = imageSizeCalculator;
defaultSize = dimensions.defaultSize;
@@ -75,7 +77,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor
public Attachment getMessageAttachment(AttachmentHeader h)
throws DbException {
return messagingManager.getAttachment(h);
return attachmentReader.getAttachment(h);
}
@Override
@@ -86,13 +88,11 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
boolean needsSize = headers.size() == 1;
for (AttachmentHeader h : headers) {
// try cache for existing item live data
MutableLiveData<AttachmentItem> liveData;
if (needsSize) liveData = itemsWithSize.get(h.getMessageId());
else {
// try items with size first, as they work as well
liveData = itemsWithSize.get(h.getMessageId());
if (liveData == null)
liveData = itemsWithoutSize.get(h.getMessageId());
MutableLiveData<AttachmentItem> liveData =
itemsWithSize.get(h.getMessageId());
if (!needsSize && liveData == null) {
// check cache for items that don't need the size
liveData = itemsWithoutSize.get(h.getMessageId());
}
// create new live data with LOADING item if cache miss
@@ -131,7 +131,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
// If a live data is already cached we don't need to do anything
if (itemsWithSize.containsKey(h.getMessageId())) return;
try {
Attachment a = messagingManager.getAttachment(h);
Attachment a = attachmentReader.getAttachment(h);
AttachmentItem item = createAttachmentItem(a, true);
MutableLiveData<AttachmentItem> liveData =
new MutableLiveData<>(item);
@@ -173,7 +173,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
Attachment a;
AttachmentItem item;
try {
a = messagingManager.getAttachment(h);
a = attachmentReader.getAttachment(h);
item = createAttachmentItem(a, needsSize);
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
@@ -210,26 +210,30 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) {
// calculate thumbnail size
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
if (!size.error) {
Size thumbnailSize =
new Size(defaultSize, defaultSize, size.getMimeType());
if (!size.hasError()) {
thumbnailSize =
getThumbnailSize(size.width, size.height, size.mimeType);
getThumbnailSize(size.getWidth(), size.getHeight(),
size.getMimeType());
}
// get file extension
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
boolean hasError = extension == null || size.error;
if (!h.getContentType().equals(size.mimeType)) {
String extension =
imageHelper.getExtensionFromMimeType(size.getMimeType());
boolean hasError = extension == null || size.hasError();
if (!h.getContentType().equals(size.getMimeType())) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Header has different mime type (" +
h.getContentType() + ") than image (" + size.mimeType +
").");
h.getContentType() + ") than image (" +
size.getMimeType() + ").");
}
hasError = true;
}
if (extension == null) extension = "";
State state = hasError ? ERROR : AVAILABLE;
return new AttachmentItem(h, size.width, size.height,
extension, thumbnailSize.width, thumbnailSize.height, state);
return new AttachmentItem(h, size.getWidth(), size.getHeight(),
extension, thumbnailSize.getWidth(), thumbnailSize.getHeight(),
state);
}
private Size getThumbnailSize(int width, int height, String mimeType) {

View File

@@ -1,23 +0,0 @@
package org.briarproject.briar.android.attachment;
class Size {
final int width;
final int height;
final String mimeType;
final boolean error;
Size(int width, int height, String mimeType) {
this.width = width;
this.height = height;
this.mimeType = mimeType;
this.error = false;
}
Size() {
this.width = 0;
this.height = 0;
this.mimeType = "";
this.error = true;
}
}

View File

@@ -0,0 +1,24 @@
package org.briarproject.briar.android.attachment;
import android.net.Uri;
import java.io.IOException;
public class UnsupportedMimeTypeException extends IOException {
private final String mimeType;
private final Uri uri;
public UnsupportedMimeTypeException(String mimeType, Uri uri) {
this.mimeType = mimeType;
this.uri = uri;
}
public String getMimeType() {
return mimeType;
}
public Uri getUri() {
return uri;
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.briar.android.attachment.media;
import android.graphics.Bitmap;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
public interface ImageCompressor {
/**
* The MIME type of compressed images
*/
String MIME_TYPE = "image/jpeg";
/**
* Load an image from {@code is}, compress it and return an InputStream
* from which the resulting image can be read. The image will be compressed
* as a JPEG image such that it fits into a message.
*
* @param is the stream to read the source image from
* @param contentType the mimetype of the source image such as "image/jpeg"
* as obtained by {@link android.content.ContentResolver#getType(Uri)}
* @return a stream from which the resulting image can be read
*/
InputStream compressImage(InputStream is, String contentType)
throws IOException;
/**
* Compress an image and return an InputStream from which the resulting
* image can be read. The image will be compressed as a JPEG image such that
* it fits into a message.
*
* @param bitmap the source image
* @return a stream from which the resulting image can be read
*/
InputStream compressImage(Bitmap bitmap) throws IOException;
}

View File

@@ -0,0 +1,92 @@
package org.briarproject.briar.android.attachment.media;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.graphics.Bitmap.CompressFormat.JPEG;
import static android.graphics.BitmapFactory.decodeStream;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE;
class ImageCompressorImpl implements ImageCompressor {
private static final Logger LOG =
getLogger(ImageCompressorImpl.class.getName());
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
private final ImageSizeCalculator imageSizeCalculator;
@Inject
ImageCompressorImpl(ImageSizeCalculator imageSizeCalculator) {
this.imageSizeCalculator = imageSizeCalculator;
}
@Override
public InputStream compressImage(InputStream is, String contentType)
throws IOException {
try {
Bitmap bitmap =
createBitmap(is, contentType, MAX_ATTACHMENT_DIMENSION);
return compressImage(bitmap);
} finally {
tryToClose(is, LOG, WARNING);
}
}
@Override
public InputStream compressImage(Bitmap bitmap) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int quality = 100; quality >= 0; quality -= 10) {
if (!bitmap.compress(JPEG, quality, out))
throw new IOException();
if (out.size() <= MAX_IMAGE_SIZE) {
if (LOG.isLoggable(INFO)) {
LOG.info("Compressed image to "
+ out.size() + " bytes, quality " + quality);
}
return new ByteArrayInputStream(out.toByteArray());
}
out.reset();
}
throw new IOException();
}
private Bitmap createBitmap(InputStream is, String contentType, int maxSize)
throws IOException {
is = new BufferedInputStream(is);
Size size = imageSizeCalculator.getSize(is, contentType);
if (size.hasError()) throw new IOException();
if (LOG.isLoggable(INFO))
LOG.info("Original image size: " + size.getWidth() + "x" +
size.getHeight());
int dimension = Math.max(size.getWidth(), size.getHeight());
int inSampleSize = 1;
while (dimension > maxSize) {
inSampleSize *= 2;
dimension /= 2;
}
if (LOG.isLoggable(INFO))
LOG.info("Scaling attachment by factor of " + inSampleSize);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
if (contentType.equals("image/png"))
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = decodeStream(is, null, options);
if (bitmap == null) throw new IOException();
return bitmap;
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import android.graphics.BitmapFactory;
import android.webkit.MimeTypeMap;

View File

@@ -0,0 +1,18 @@
package org.briarproject.briar.android.attachment.media;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream;
@NotNullByDefault
public interface ImageSizeCalculator {
/**
* Determine the size of the image that can be read from {@code is}.
*
* @param contentType the mime type of the image. If "image/jpeg" is passed,
* the implementation will try to determine the size from the Exif header
*/
Size getSize(InputStream is, String contentType);
}

View File

@@ -1,9 +1,9 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import com.bumptech.glide.util.MarkEnforcingInputStream;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
import org.briarproject.briar.android.attachment.media.ImageHelper.DecodeResult;
import java.io.IOException;
import java.io.InputStream;
@@ -23,20 +23,21 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class ImageSizeCalculator {
class ImageSizeCalculatorImpl implements ImageSizeCalculator {
private static final Logger LOG =
getLogger(ImageSizeCalculator.class.getName());
getLogger(ImageSizeCalculatorImpl.class.getName());
private static final int READ_LIMIT = 1024 * 8192;
private final ImageHelper imageHelper;
ImageSizeCalculator(ImageHelper imageHelper) {
ImageSizeCalculatorImpl(ImageHelper imageHelper) {
this.imageHelper = imageHelper;
}
Size getSize(InputStream is, String contentType) {
@Override
public Size getSize(InputStream is, String contentType) {
Size size = new Size();
is = new MarkEnforcingInputStream(is);
is.mark(READ_LIMIT);
@@ -49,7 +50,7 @@ class ImageSizeCalculator {
logException(LOG, WARNING, e);
}
}
if (size.error) {
if (size.hasError()) {
// need to mark again to re-add read limit
is.mark(READ_LIMIT);
try {

View File

@@ -0,0 +1,24 @@
package org.briarproject.briar.android.attachment.media;
import dagger.Module;
import dagger.Provides;
@Module
public class MediaModule {
@Provides
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
return imageHelper;
}
@Provides
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
return new ImageSizeCalculatorImpl(imageHelper);
}
@Provides
ImageCompressor provideImageCompressor(
ImageCompressorImpl imageCompressor) {
return imageCompressor;
}
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.briar.android.attachment.media;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class Size {
private final int width;
private final int height;
private final String mimeType;
private final boolean error;
public Size(int width, int height, String mimeType) {
this.width = width;
this.height = height;
this.mimeType = mimeType;
this.error = false;
}
public Size() {
this.width = 0;
this.height = 0;
this.mimeType = "";
this.error = true;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getMimeType() {
return mimeType;
}
public boolean hasError() {
return error;
}
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.blog.BlogPostHeader;

View File

@@ -20,7 +20,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List;
@@ -156,16 +156,16 @@ public class ReblogFragment extends BaseFragment implements SendListener {
progressBar = v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
}, getFragmentManager());
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
}, getFragmentManager());
input = v.findViewById(R.id.inputText);
}
}

View File

@@ -19,10 +19,10 @@ import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.security.GeneralSecurityException;
import java.util.List;

View File

@@ -3,14 +3,12 @@ package org.briarproject.briar.android.contact;
import android.content.Context;
import android.view.View;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.briar.android.util.BriarAdapter;
import javax.annotation.Nullable;
import androidx.annotation.NonNull;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
public abstract class BaseContactListAdapter<I extends ContactItem, VH extends ContactItemViewHolder<I>>
@@ -47,15 +45,6 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
return true;
}
int findItemPosition(ContactId c) {
for (int i = 0; i < getItemCount(); i++) {
I item = getItemAt(i);
if (item != null && item.getContact().getId().equals(c))
return i;
}
return INVALID_POSITION; // Not found
}
public interface OnContactClickListener<I> {
void onItemClick(View view, I item);
}

View File

@@ -2,22 +2,26 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.Immutable;
@NotThreadSafe
@Immutable
@NotNullByDefault
public class ContactItem {
private final Contact contact;
private boolean connected;
private final AuthorInfo authorInfo;
private final boolean connected;
public ContactItem(Contact contact) {
this(contact, false);
public ContactItem(Contact contact, AuthorInfo authorInfo) {
this(contact, authorInfo, false);
}
public ContactItem(Contact contact, boolean connected) {
public ContactItem(Contact contact, AuthorInfo authorInfo,
boolean connected) {
this.contact = contact;
this.authorInfo = authorInfo;
this.connected = connected;
}
@@ -25,12 +29,12 @@ public class ContactItem {
return contact;
}
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
boolean isConnected() {
return connected;
}
void setConnected(boolean connected) {
this.connected = connected;
}
}

View File

@@ -5,7 +5,6 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -14,9 +13,9 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
import im.delight.android.identicons.IdenticonDrawable;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
@UiThread
@NotNullByDefault
@@ -40,9 +39,7 @@ public class ContactItemViewHolder<I extends ContactItem>
}
protected void bind(I item, @Nullable OnContactClickListener<I> listener) {
Author author = item.getContact().getAuthor();
avatar.setImageDrawable(
new IdenticonDrawable(author.getId().getBytes()));
setAvatar(avatar, item);
name.setText(getContactDisplayName(item.getContact()));
if (bulb != null) {

View File

@@ -1,50 +1,73 @@
package org.briarproject.briar.android.contact;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.ListAdapter;
@NotNullByDefault
public class ContactListAdapter extends
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
ListAdapter<ContactListItem, ContactListItemViewHolder> {
public ContactListAdapter(Context context,
// TODO: using the click listener interface from BaseContactListAdapter on
// purpose here because it is entangled with ContactListItemViewHolder. At
// some point we probably want to change that.
protected final OnContactClickListener<ContactListItem> listener;
public ContactListAdapter(
OnContactClickListener<ContactListItem> listener) {
super(context, ContactListItem.class, listener);
super(new ContactListCallback());
this.listener = listener;
}
@NotNullByDefault
private static class ContactListCallback
extends ItemCallback<ContactListItem> {
@Override
public boolean areItemsTheSame(ContactListItem c1, ContactListItem c2) {
return c1.getContact().equals(c2.getContact());
}
@Override
public boolean areContentsTheSame(ContactListItem c1,
ContactListItem c2) {
// check for all properties that influence visual
// representation of contact
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false;
}
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
if (c1.isConnected() != c2.isConnected()) {
return false;
}
return NullSafety.equals(c1.getAuthorInfo().getAvatarHeader(),
c2.getAuthorInfo().getAvatarHeader());
}
}
@Override
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
int viewType) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_contact, viewGroup, false);
return new ContactListItemViewHolder(v);
}
@Override
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
// check for all properties that influence visual
// representation of contact
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false;
}
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
return c1.isConnected() == c2.isConnected();
public void onBindViewHolder(ContactListItemViewHolder viewHolder,
int position) {
viewHolder.bind(getItem(position), listener);
}
@Override
public int compare(ContactListItem c1, ContactListItem c2) {
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
}
}

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