Compare commits

...

117 Commits

Author SHA1 Message Date
Daniel Lublin
bb8333660b Remember to return 2021-01-29 11:02:13 +01:00
Daniel Lublin
9925c799a1 WIP add old BT-diagram to BT-setup screen 2021-01-28 15:55:26 +01:00
Daniel Lublin
3ee2d2112c WIP 2021-01-28 11:08:34 +01:00
Daniel Lublin
da5b2c194a WIP 2021-01-28 11:08:33 +01:00
Daniel Lublin
244d03a718 WIP mock 2021-01-28 11:08:32 +01: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
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
akwizgran
5fdc7e7cc4 Bump version numbers for 1.2.13 release. 2021-01-07 16:23:11 +00:00
akwizgran
7569d5ffb3 Update translations. 2021-01-07 16:21:59 +00:00
akwizgran
deca5d56cc Merge branch '1885-malformed-links' into 'master'
Do not produce malformed links for adding contacts when on other locales such as Turkish

Closes #1885

See merge request briar/briar!1335
2021-01-07 15:07:55 +00:00
Torsten Grote
3d6b48bb34 Do not produce malformed links for adding contacts when on other locales
such as Turkish
2021-01-07 11:52:27 -03:00
akwizgran
0dc631b7a8 Merge branch '1869-forum-list-view-model' into 'master'
Introduce ViewModel for ForumListFragment

Closes #1869

See merge request briar/briar!1331
2021-01-07 14:45:47 +00:00
Torsten Grote
921e952b05 Rename ForumItem to ForumPostItem 2021-01-07 08:58:14 -03:00
Torsten Grote
3b02797639 Block forum post notifications while viewing forum list 2021-01-07 08:58:14 -03:00
Torsten Grote
e2e67edbbe Introduce ForumListViewModel 2021-01-07 08:58:13 -03:00
Torsten Grote
a9cd40faeb Add transactions to methods in ForumManager 2021-01-07 08:58:13 -03: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
akwizgran
04517e942e Merge branch '1753-query-filter' into 'master'
Define manifest <queries> allowing us to make intent queries on API 30+

See merge request briar/briar!1323
2021-01-05 14:47:00 +00:00
akwizgran
9a25ad892d Merge branch '1753-screen-filter' into 'master'
Prepare screen overlay warning for targeting API 30

See merge request briar/briar!1322
2021-01-05 14:31:10 +00:00
akwizgran
3457d8f9ab Merge branch '1861-no-wifi-networks' into 'master'
Remove calls to WifiManager#getConfiguredNetworks()

Closes #1861

See merge request briar/briar!1330
2021-01-05 14:03:40 +00:00
Torsten Grote
5fb2624ffa Remove calls to WifiManager#getConfiguredNetworks()
as these require fine location permission now and don't work when
location services are disabled.
2021-01-05 10:22:32 -03:00
akwizgran
ed9a7bec2c Merge branch '1800-group-list-view-model' into 'master'
Using ListAdapter for PrivateGroupList

See merge request briar/briar!1327
2021-01-05 11:25:33 +00:00
Torsten Grote
ff70315d5c Address small things found in code review
of group list view model migration.
2021-01-04 16:19:29 -03:00
Torsten Grote
f197243273 Block all group message notifications while viewing list of private groups 2021-01-04 15:56:37 -03:00
Torsten Grote
6409a3b179 Refactor handleDbException to handleException 2021-01-04 15:39:02 -03:00
Torsten Grote
f882e46b33 Make GroupItem immutable and introduce copy constructors 2021-01-04 15:22:31 -03:00
akwizgran
efa63c306a Merge branch '1800-db-view-model' into 'master'
Introduce DbViewModel as replacement of DbController

See merge request briar/briar!1326
2021-01-04 14:00:42 +00:00
Torsten Grote
205b4f77b2 Add beginning of a ViewModel test
mostly to demonstrate how those could look like
2020-12-18 14:42:33 -03:00
Torsten Grote
015ecb1d99 Migrate GroupListController to a ViewModel
Use ListAdapter to calculate list diffs on a background thread
2020-12-17 17:40:24 -03:00
Torsten Grote
fd86b73626 Load list of private groups in a single DB transaction 2020-12-17 17:40:24 -03:00
Torsten Grote
9048392d4e Add methods to DbViewModel for loading and updating lists of items 2020-12-17 17:40:23 -03:00
Torsten Grote
480aaaa35e Introduce DbViewModel as replacement of DbController 2020-12-16 15:23:05 -03:00
Torsten Grote
002feb8e29 Merge branch '1720-add-up-button-to-feedback-activity' into 'master'
Add "up navigation" button to FeedbackActivity

See merge request briar/briar!1325
2020-12-16 16:52:34 +00:00
akwizgran
c6ba2b037a Add "up navigation" button to FeedbackActivity. 2020-12-16 16:04:08 +00:00
Torsten Grote
98788c7c80 Define manifest <queries> allowing us to make intent queries on API 30+ 2020-12-14 12:01:56 -03:00
Torsten Grote
e6f66ebc95 Screen overlay warning: remove ability to query and remember allowed apps for API 30+
as we can't query all installed apps anymore when targeting API 30
2020-12-14 10:53:12 -03:00
akwizgran
04485e58da Merge branch '1720-no-acra' into 'master'
Remove ACRA and implement the few bits we need ourselves

Closes #1114, #1720, and #1793

See merge request briar/briar!1319
2020-12-14 13:34:11 +00:00
Torsten Grote
97118fd92b Kill crash reporter process only with some delay 2020-12-14 09:14:42 -03:00
akwizgran
ac4fbf202f Fix duplicate DeviceInfo key. 2020-12-11 16:40:55 +00:00
akwizgran
b81495eac1 Use JSON bools and numbers, use fixed format for dates, normalise JSON keys. 2020-12-11 16:30:29 +00:00
akwizgran
db90f75d2e Remove unused string, remove periods from single-sentence toasts. 2020-12-11 16:29:29 +00:00
Torsten Grote
bed3abfd40 Address review feedback for ACRA replacement 2020-12-11 10:50:39 -03:00
Torsten Grote
0967f6c48e Merge branch '1794-tell-tor-about-ipv6-only-networks' into 'master'
Tell Tor when we're on an IPv6-only network

Closes #1794

See merge request briar/briar!1320
2020-12-10 18:24:45 +00:00
Torsten Grote
f9a8fcb207 Move Android version from basic info to device info
because the basic info is always sent and we say there won't be data of the device in what we send.
2020-12-10 14:40:33 -03:00
Torsten Grote
eb3c2a3566 Remove ACRA and implement the few bits we need ourselves 2020-12-10 14:29:25 -03:00
akwizgran
02ee678bab If using bridges, use meek if the network is IPv6-only. 2020-11-03 13:52:12 +00:00
akwizgran
f6bdbb1b80 Let Tor know if we're on an IPv6-only network. 2020-11-03 13:44:57 +00:00
347 changed files with 9035 additions and 3624 deletions

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-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-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 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-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" />
</method> </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' apply from: 'witness.gradle'
android { android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion 30
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion '30.0.2'
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion 29
versionCode rootProject.ext.versionCode versionCode 10213
versionName rootProject.ext.versionName versionName "1.2.13"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -1,11 +1,15 @@
package org.briarproject.bramble.network; package org.briarproject.bramble.network;
import android.annotation.TargetApi;
import android.app.Application; import android.app.Application;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
@@ -19,6 +23,11 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable; import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -36,16 +45,22 @@ import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION; import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class AndroidNetworkManager implements NetworkManager, Service { class AndroidNetworkManager implements NetworkManager, Service {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidNetworkManager.class.getName()); getLogger(AndroidNetworkManager.class.getName());
// See android.net.wifi.WifiManager // See android.net.wifi.WifiManager
private static final String WIFI_AP_STATE_CHANGED_ACTION = private static final String WIFI_AP_STATE_CHANGED_ACTION =
@@ -54,7 +69,8 @@ class AndroidNetworkManager implements NetworkManager, Service {
private final TaskScheduler scheduler; private final TaskScheduler scheduler;
private final EventBus eventBus; private final EventBus eventBus;
private final Executor eventExecutor; private final Executor eventExecutor;
private final Context appContext; private final Application app;
private final ConnectivityManager connectivityManager;
private final AtomicReference<Cancellable> connectivityCheck = private final AtomicReference<Cancellable> connectivityCheck =
new AtomicReference<>(); new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
@@ -67,7 +83,9 @@ class AndroidNetworkManager implements NetworkManager, Service {
this.scheduler = scheduler; this.scheduler = scheduler;
this.eventBus = eventBus; this.eventBus = eventBus;
this.eventExecutor = eventExecutor; this.eventExecutor = eventExecutor;
this.appContext = app.getApplicationContext(); this.app = app;
connectivityManager = (ConnectivityManager)
requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE));
} }
@Override @Override
@@ -82,24 +100,82 @@ class AndroidNetworkManager implements NetworkManager, Service {
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(networkStateReceiver, filter); app.registerReceiver(networkStateReceiver, filter);
} }
@Override @Override
public void stopService() { public void stopService() {
if (networkStateReceiver != null) if (networkStateReceiver != null)
appContext.unregisterReceiver(networkStateReceiver); app.unregisterReceiver(networkStateReceiver);
} }
@Override @Override
public NetworkStatus getNetworkStatus() { public NetworkStatus getNetworkStatus() {
ConnectivityManager cm = (ConnectivityManager) NetworkInfo net = connectivityManager.getActiveNetworkInfo();
appContext.getSystemService(CONNECTIVITY_SERVICE);
if (cm == null) throw new AssertionError();
NetworkInfo net = cm.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected(); boolean connected = net != null && net.isConnected();
boolean wifi = connected && net.getType() == TYPE_WIFI; boolean wifi = false, ipv6Only = false;
return new NetworkStatus(connected, wifi); if (connected) {
wifi = net.getType() == TYPE_WIFI;
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
else ipv6Only = areAllAvailableNetworksIpv6Only();
}
return new NetworkStatus(connected, wifi, ipv6Only);
}
/**
* Returns true if the
* {@link ConnectivityManager#getActiveNetwork() active network} has an
* IPv6 unicast address and no IPv4 addresses. The active network is
* assumed not to be a loopback interface.
*/
@TargetApi(23)
private boolean isActiveNetworkIpv6Only() {
Network net = connectivityManager.getActiveNetwork();
if (net == null) {
LOG.info("No active network");
return false;
}
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) {
LOG.info("No link properties for active network");
return false;
}
boolean hasIpv6Unicast = false;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
return hasIpv6Unicast;
}
/**
* Returns true if the device has at least one network interface with an
* IPv6 unicast address and no interfaces with IPv4 addresses, excluding
* loopback interfaces and interfaces that are
* {@link NetworkInterface#isUp() down}. If this method returns true and
* the device has internet access then it's via IPv6 only.
*/
private boolean areAllAvailableNetworksIpv6Only() {
try {
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
if (interfaces == null) {
LOG.info("No network interfaces");
return false;
}
boolean hasIpv6Unicast = false;
for (NetworkInterface i : list(interfaces)) {
if (i.isLoopback() || !i.isUp()) continue;
for (InetAddress addr : list(i.getInetAddresses())) {
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
}
return hasIpv6Unicast;
} catch (SocketException e) {
logException(LOG, WARNING, e);
return false;
}
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {

View File

@@ -6,8 +6,6 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build; import android.os.Build;
import android.os.Parcel; import android.os.Parcel;
import android.os.StrictMode; import android.os.StrictMode;
@@ -17,12 +15,10 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.content.Context.WIFI_SERVICE;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.Secure.ANDROID_ID; import static android.provider.Settings.Secure.ANDROID_ID;
@@ -52,15 +48,6 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
String id = Settings.Secure.getString(contentResolver, ANDROID_ID); String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id); if (id != null) out.writeUTF(id);
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();
WifiManager wm = (WifiManager) appContext.getApplicationContext()
.getSystemService(WIFI_SERVICE);
if (wm != null) {
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
if (configs != null) {
for (WifiConfiguration config : configs)
parcel.writeParcelable(config, 0);
}
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) { if (bt != null) {
for (BluetoothDevice device : bt.getBondedDevices()) for (BluetoothDevice device : bt.getBondedDevices())

View File

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

@@ -6,4 +6,7 @@ package org.briarproject.bramble.api;
public interface FeatureFlags { public interface FeatureFlags {
boolean shouldEnableImageAttachments(); 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.db.Transaction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -179,6 +178,11 @@ public interface ContactManager {
*/ */
Collection<Contact> getContacts() throws DbException; Collection<Contact> getContacts() throws DbException;
/**
* Returns all contacts.
*/
Collection<Contact> getContacts(Transaction txn) throws DbException;
/** /**
* Removes a contact and all associated state. * Removes a contact and all associated state.
*/ */
@@ -215,16 +219,6 @@ public interface ContactManager {
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId) boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException; 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 { interface ContactHook {
/** /**

View File

@@ -8,11 +8,12 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class NetworkStatus { public class NetworkStatus {
private final boolean connected, wifi; private final boolean connected, wifi, ipv6Only;
public NetworkStatus(boolean connected, boolean wifi) { public NetworkStatus(boolean connected, boolean wifi, boolean ipv6Only) {
this.connected = connected; this.connected = connected;
this.wifi = wifi; this.wifi = wifi;
this.ipv6Only = ipv6Only;
} }
public boolean isConnected() { public boolean isConnected() {
@@ -22,4 +23,8 @@ public class NetworkStatus {
public boolean isWifi() { public boolean isWifi() {
return wifi; return wifi;
} }
public boolean isIpv6Only() {
return ipv6Only;
}
} }

View File

@@ -29,4 +29,12 @@ public class NullSafety {
public static void requireNull(@Nullable Object o) { public static void requireNull(@Nullable Object o) {
if (o != null) throw new AssertionError(); 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

@@ -23,6 +23,8 @@ public interface DevReporter {
/** /**
* Sends any reports previously stored on disk. * Sends any reports previously stored on disk.
*
* @return The number of reports that were sent.
*/ */
void sendReports(); int sendReports();
} }

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.event.EventListener;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeyManager; 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.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.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; import static org.briarproject.bramble.util.StringUtils.toUtf8;
@ThreadSafe @ThreadSafe
@@ -213,6 +207,11 @@ class ContactManagerImpl implements ContactManager, EventListener {
return db.transactionWithResult(true, db::getContacts); return db.transactionWithResult(true, db::getContacts);
} }
@Override
public Collection<Contact> getContacts(Transaction txn) throws DbException {
return db.getContacts(txn);
}
@Override @Override
public void removeContact(ContactId c) throws DbException { public void removeContact(ContactId c) throws DbException {
db.transaction(false, txn -> removeContact(txn, c)); db.transaction(false, txn -> removeContact(txn, c));
@@ -256,25 +255,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
db.removeContact(txn, c); 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 @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof PendingContactStateChangedEvent) { if (e instanceof PendingContactStateChangedEvent) {

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.Base32; import org.briarproject.bramble.util.Base32;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Locale;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import javax.inject.Inject; import javax.inject.Inject;
@@ -52,7 +53,7 @@ class PendingContactFactoryImpl implements PendingContactFactory {
byte[] raw = new byte[RAW_LINK_BYTES]; byte[] raw = new byte[RAW_LINK_BYTES];
raw[0] = FORMAT_VERSION; raw[0] = FORMAT_VERSION;
arraycopy(encoded, 0, raw, 1, encoded.length); arraycopy(encoded, 0, raw, 1, encoded.length);
return "briar://" + Base32.encode(raw).toLowerCase(); return "briar://" + Base32.encode(raw).toLowerCase(Locale.US);
} }
private PublicKey parseHandshakeLink(String link) throws FormatException { private PublicKey parseHandshakeLink(String link) throws FormatException {

View File

@@ -863,6 +863,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!state.isTorRunning()) return; if (!state.isTorRunning()) return;
boolean online = status.isConnected(); boolean online = status.isConnected();
boolean wifi = status.isWifi(); boolean wifi = status.isWifi();
boolean ipv6Only = status.isIpv6Only();
String country = locationUtils.getCurrentCountry(); String country = locationUtils.getCurrentCountry();
boolean blocked = boolean blocked =
circumventionProvider.isTorProbablyBlocked(country); circumventionProvider.isTorProbablyBlocked(country);
@@ -879,7 +880,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC; boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi); LOG.info("Online: " + online + ", wifi: " + wifi
+ ", IPv6 only: " + ipv6Only);
if (country.isEmpty()) LOG.info("Country code unknown"); if (country.isEmpty()) LOG.info("Country code unknown");
else LOG.info("Country code: " + country); else LOG.info("Country code: " + country);
LOG.info("Charging: " + charging); LOG.info("Charging: " + charging);
@@ -916,7 +918,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true; enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES || if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) { (automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) { if (ipv6Only ||
circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges"); LOG.info("Using meek bridges");
enableBridges = true; enableBridges = true;
useMeek = true; useMeek = true;
@@ -942,6 +945,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (enableNetwork) { if (enableNetwork) {
enableBridges(enableBridges, useMeek); enableBridges(enableBridges, useMeek);
enableConnectionPadding(enableConnectionPadding); enableConnectionPadding(enableConnectionPadding);
useIpv6(ipv6Only);
} }
enableNetwork(enableNetwork); enableNetwork(enableNetwork);
} catch (IOException e) { } catch (IOException e) {
@@ -954,6 +958,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} }
private void useIpv6(boolean ipv6Only) throws IOException {
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
}
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
protected class PluginState { protected class PluginState {

View File

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

View File

@@ -29,6 +29,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -100,11 +101,12 @@ class DevReporterImpl implements DevReporter, EventListener {
} }
@Override @Override
public void sendReports() { public int sendReports() {
File reportDir = devConfig.getReportDir(); File reportDir = devConfig.getReportDir();
File[] reports = reportDir.listFiles(); File[] reports = reportDir.listFiles();
int reportsSent = 0;
if (reports == null || reports.length == 0) if (reports == null || reports.length == 0)
return; // No reports to send return reportsSent; // No reports to send
LOG.info("Sending reports to developers"); LOG.info("Sending reports to developers");
for (File f : reports) { for (File f : reports) {
@@ -116,13 +118,15 @@ class DevReporterImpl implements DevReporter, EventListener {
in = new FileInputStream(f); in = new FileInputStream(f);
IoUtils.copyAndClose(in, out); IoUtils.copyAndClose(in, out);
f.delete(); f.delete();
reportsSent++;
} catch (IOException e) { } catch (IOException e) {
LOG.log(WARNING, "Failed to send reports", e); LOG.log(WARNING, "Failed to send reports", e);
tryToClose(out, LOG, WARNING); tryToClose(out, LOG, WARNING);
tryToClose(in, LOG, WARNING); tryToClose(in, LOG, WARNING);
return; return reportsSent;
} }
} }
LOG.info("Reports sent"); if (LOG.isLoggable(INFO)) LOG.info(reportsSent + " report(s) sent");
return reportsSent;
} }
} }

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 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 meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
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

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.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; 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.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; 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.HandshakeLinkConstants.BASE32_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; 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.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.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getAuthor; 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.getRandomBase32String;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class ContactManagerImplTest extends BrambleMockTestCase { public class ContactManagerImplTest extends BrambleMockTestCase {
@@ -212,75 +204,6 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
assertTrue(contactManager.contactExists(remote.getId(), local)); 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 @Test
public void testGetHandshakeLink() throws Exception { public void testGetHandshakeLink() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);

View File

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

View File

@@ -5,6 +5,8 @@ import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.util.Enumeration; import java.util.Enumeration;
@@ -14,8 +16,8 @@ import javax.inject.Inject;
import static java.net.NetworkInterface.getNetworkInterfaces; import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list; import static java.util.Collections.list;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -23,7 +25,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class JavaNetworkManager implements NetworkManager { class JavaNetworkManager implements NetworkManager {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(JavaNetworkManager.class.getName()); getLogger(JavaNetworkManager.class.getName());
@Inject @Inject
JavaNetworkManager() { JavaNetworkManager() {
@@ -31,26 +33,28 @@ class JavaNetworkManager implements NetworkManager {
@Override @Override
public NetworkStatus getNetworkStatus() { public NetworkStatus getNetworkStatus() {
boolean connected = false; boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false;
try { try {
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces(); Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
if (interfaces != null) { if (interfaces == null) {
LOG.info("No network interfaces");
} else {
for (NetworkInterface i : list(interfaces)) { for (NetworkInterface i : list(interfaces)) {
if (i.isLoopback()) continue; if (i.isLoopback() || !i.isUp()) continue;
if (i.isUp() && i.getInetAddresses().hasMoreElements()) { for (InetAddress addr : list(i.getInetAddresses())) {
if (LOG.isLoggable(INFO)) {
LOG.info("Interface " + i.getDisplayName() +
" is up with at least one address.");
}
connected = true; connected = true;
break; if (addr instanceof Inet4Address) {
hasIpv4 = true;
} else if (!addr.isMulticastAddress()) {
hasIpv6Unicast = true;
}
} }
} }
} }
} catch (SocketException e) { } catch (SocketException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
return new NetworkStatus(connected, false); return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast);
} }
} }

View File

@@ -16,14 +16,14 @@ def getStdout = { command, defaultValue ->
} }
android { android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion 30
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion '30.0.2'
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion 29
versionCode rootProject.ext.versionCode versionCode 10213
versionName rootProject.ext.versionName versionName "1.2.13"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -100,7 +100,6 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc03' implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc03'
implementation 'ch.acra:acra:4.11'
implementation 'info.guardianproject.panic:panic:1.0' implementation 'info.guardianproject.panic:panic:1.0'
implementation 'info.guardianproject.trustedintents:trustedintents:0.2' implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:3.0.1' implementation 'de.hdodenhof:circleimageview:3.0.1'
@@ -109,7 +108,7 @@ dependencies {
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21 implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1' implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0' 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") { implementation("com.github.bumptech.glide:glide:$glideVersion") {
exclude group: 'com.android.support' exclude group: 'com.android.support'
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
@@ -127,10 +126,11 @@ dependencies {
testImplementation 'androidx.test:runner:1.3.0' testImplementation 'androidx.test:runner:1.3.0'
testImplementation 'androidx.test.ext:junit:1.1.2' testImplementation 'androidx.test.ext:junit:1.1.2'
testImplementation 'androidx.fragment:fragment-testing:1.2.5' testImplementation 'androidx.fragment:fragment-testing:1.2.5'
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion" testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
testImplementation 'org.robolectric:robolectric:4.3.1' testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'org.mockito:mockito-core:3.1.0' testImplementation 'org.mockito:mockito-core:3.1.0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13.1'
testImplementation "org.jmock:jmock:$jmockVersion" testImplementation "org.jmock:jmock:$jmockVersion"
testImplementation "org.jmock:jmock-junit4:$jmockVersion" testImplementation "org.jmock:jmock-junit4:$jmockVersion"
testImplementation "org.jmock:jmock-legacy:$jmockVersion" testImplementation "org.jmock:jmock-legacy:$jmockVersion"
@@ -142,11 +142,9 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24" androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0' androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'junit:junit:4.13.1'
androidTestScreenshotImplementation('tools.fastlane:screengrab:1.2.0') { androidTestScreenshotImplementation 'tools.fastlane:screengrab:2.0.0'
// workaround for jetifier issue https://issuetracker.google.com/issues/123060356 androidTestScreenshotImplementation 'com.jraska:falcon:2.1.1'
exclude group: 'com.android.support.test.uiautomator'
}
androidTestScreenshotImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' 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.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest; import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -15,6 +16,7 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
AppModule.class, AppModule.class,
AttachmentModule.class, AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.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; package org.briarproject.briar.android.attachment;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.android.attachment.media.ImageHelper;
import org.briarproject.briar.api.messaging.AttachmentHeader; 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.io.InputStream; import java.io.InputStream;
import java.util.Random; import java.util.Random;
import javax.inject.Inject;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import static androidx.test.InstrumentationRegistry.getContext; import static androidx.test.InstrumentationRegistry.getContext;
@@ -24,16 +29,28 @@ public class AttachmentRetrieverIntegrationTest {
private final AttachmentDimensions dimensions = new AttachmentDimensions( private final AttachmentDimensions dimensions = new AttachmentDimensions(
100, 50, 200, 75, 300 100, 50, 200, 75, 300
); );
private final GroupId groupId = new GroupId(getRandomId());
private final MessageId msgId = new MessageId(getRandomId()); private final MessageId msgId = new MessageId(getRandomId());
private final ImageHelper imageHelper = new ImageHelperImpl(); @Inject
private final AttachmentRetriever retriever = ImageHelper imageHelper;
new AttachmentRetrieverImpl(null, null, dimensions, imageHelper, @Inject
new ImageSizeCalculator(imageHelper)); 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 @Test
public void testSmallJpegImage() throws Exception { 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"); InputStream is = getAssetInputStream("kitten_small.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -49,7 +66,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testBigJpegImage() throws Exception { 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"); InputStream is = getAssetInputStream("kitten_original.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -65,7 +82,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testSmallPngImage() throws Exception { 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"); InputStream is = getAssetInputStream("kitten.png");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -81,7 +98,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testUberGif() throws Exception { 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"); InputStream is = getAssetInputStream("uber.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -96,7 +113,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testLottaPixels() throws Exception { 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"); InputStream is = getAssetInputStream("lottapixel.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -111,7 +128,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testImageIoCrash() throws Exception { 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"); InputStream is = getAssetInputStream("image_io_crash.png");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -126,7 +143,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testGimpCrash() throws Exception { 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"); InputStream is = getAssetInputStream("gimp_crash.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -141,7 +158,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testOptiPngAfl() throws Exception { 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"); InputStream is = getAssetInputStream("opti_png_afl.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -156,7 +173,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testLibrawError() throws Exception { 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"); InputStream is = getAssetInputStream("libraw_error.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -165,7 +182,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testSmallAnimatedGifMaxDimensions() throws Exception { 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"); InputStream is = getAssetInputStream("animated.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -180,7 +197,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testSmallAnimatedGifHugeDimensions() throws Exception { 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"); InputStream is = getAssetInputStream("animated2.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -195,7 +212,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testSmallGifLargeDimensions() throws Exception { 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"); InputStream is = getAssetInputStream("error_large.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -210,7 +227,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testHighError() throws Exception { 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"); InputStream is = getAssetInputStream("error_high.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -225,7 +242,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testWideError() throws Exception { 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"); InputStream is = getAssetInputStream("error_wide.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -9,8 +9,13 @@ import static android.os.Build.VERSION.SDK_INT;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AttachmentCreationTaskTest public class ImageCompressorTest
extends AbstractAttachmentCreationTaskTest { extends AbstractImageCompressorTest {
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
@Test @Test
public void testCompressSmallPng() throws Exception { 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -17,11 +17,16 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class PngSuiteAttachmentCreationTaskTest public class PngSuiteImageCompressorTest
extends AbstractAttachmentCreationTaskTest { extends AbstractImageCompressorTest {
private static final Logger LOG = private static final Logger LOG =
getLogger(PngSuiteAttachmentCreationTaskTest.class.getName()); getLogger(PngSuiteImageCompressorTest.class.getName());
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
@Parameters @Parameters
public static Iterable<String> data() throws IOException { public static Iterable<String> data() throws IOException {
@@ -34,14 +39,14 @@ public class PngSuiteAttachmentCreationTaskTest
private final String filename; private final String filename;
public PngSuiteAttachmentCreationTaskTest(String filename) { public PngSuiteImageCompressorTest(String filename) {
this.filename = filename; this.filename = filename;
} }
@Test @Test
public void testPngSuiteCompress() throws Exception { public void testPngSuiteCompress() throws Exception {
assumeTrue(isOptionalTestEnabled( assumeTrue(isOptionalTestEnabled(
PngSuiteAttachmentCreationTaskTest.class)); PngSuiteImageCompressorTest.class));
LOG.info("Testing " + filename); LOG.info("Testing " + filename);
if (filename.startsWith("x")) { if (filename.startsWith("x")) {
try { 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.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.ConversationActivityScreenshotTest; import org.briarproject.briar.android.conversation.ConversationActivityScreenshotTest;
import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest; import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest;
@@ -16,6 +17,7 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
AppModule.class, AppModule.class,
AttachmentModule.class, AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.class, BriarAccountModule.class,
@@ -26,6 +28,7 @@ public interface BriarUiTestComponent extends AndroidComponent {
void inject(SetupDataTest test); void inject(SetupDataTest test);
void inject(ConversationActivityScreenshotTest test); void inject(ConversationActivityScreenshotTest test);
void inject(SettingsActivityScreenshotTest test); void inject(SettingsActivityScreenshotTest test);
} }

View File

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

@@ -65,30 +65,43 @@
<service <service
android:name="org.briarproject.briar.android.NotificationCleanupService" android:name="org.briarproject.briar.android.NotificationCleanupService"
android:exported="false"></service> android:exported="false" />
<activity <activity
android:name="org.briarproject.briar.android.reporting.DevReportActivity" android:name="org.briarproject.briar.android.reporting.CrashReportActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="false" android:exported="false"
android:finishOnTaskLaunch="true" android:finishOnTaskLaunch="true"
android:label="@string/crash_report_title" android:label="@string/crash_report_title"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:process=":briar_error_handler"
android:theme="@style/BriarTheme.NoActionBar" android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden"></activity> android:windowSoftInputMode="adjustResize|stateHidden" />
<activity
android:name="org.briarproject.briar.android.reporting.FeedbackActivity"
android:exported="false"
android:label="@string/feedback_title"
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity"
android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
</activity>
<activity <activity
android:name="org.briarproject.briar.android.splash.ExpiredActivity" android:name="org.briarproject.briar.android.splash.ExpiredActivity"
android:label="@string/app_name"></activity> android:label="@string/app_name" />
<activity <activity
android:name="org.briarproject.briar.android.login.StartupActivity" android:name="org.briarproject.briar.android.login.StartupActivity"
android:label="@string/app_name"></activity> android:label="@string/app_name" />
<activity <activity
android:name="org.briarproject.briar.android.account.SetupActivity" android:name="org.briarproject.briar.android.account.SetupActivity"
android:label="@string/setup_title" android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible"></activity> android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
<activity <activity
android:name="org.briarproject.briar.android.splash.SplashScreenActivity" android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
@@ -346,7 +359,7 @@
<activity <activity
android:name="org.briarproject.briar.android.StartupFailureActivity" android:name="org.briarproject.briar.android.StartupFailureActivity"
android:label="@string/startup_failed_activity_title"></activity> android:label="@string/startup_failed_activity_title" />
<activity <activity
android:name="org.briarproject.briar.android.settings.SettingsActivity" android:name="org.briarproject.briar.android.settings.SettingsActivity"
@@ -412,11 +425,11 @@
<activity <activity
android:name="org.briarproject.briar.android.logout.ExitActivity" android:name="org.briarproject.briar.android.logout.ExitActivity"
android:theme="@android:style/Theme.NoDisplay"></activity> android:theme="@android:style/Theme.NoDisplay" />
<activity <activity
android:name=".android.logout.HideUiActivity" android:name=".android.logout.HideUiActivity"
android:theme="@android:style/Theme.NoDisplay"></activity> android:theme="@android:style/Theme.NoDisplay" />
<activity <activity
android:name=".android.account.UnlockActivity" android:name=".android.account.UnlockActivity"
@@ -435,5 +448,36 @@
android:label="@string/pending_contact_requests" android:label="@string/pending_contact_requests"
android:theme="@style/BriarTheme" /> android:theme="@style/BriarTheme" />
<activity
android:name=".android.bluetoothsetup.BluetoothSetupActivity"
android:label="Bluetooth Setup"
android:theme="@style/BriarTheme"
android:windowSoftInputMode="adjustResize|stateHidden" />
</application> </application>
<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" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<!-- white-listing the intents below does not seem necessary,
but they are still included in case modified Android versions require it -->
<intent>
<action android:name="android.bluetooth.adapter.action.REQUEST_DISCOVERABLE" />
</intent>
<intent>
<action android:name="android.settings.CHANNEL_NOTIFICATION_SETTINGS" />
</intent>
</queries>
</manifest> </manifest>

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
@@ -31,14 +32,15 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader; import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.android.view.EmojiTextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager; import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor; 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.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -47,6 +49,7 @@ import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.feed.FeedManager; import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager; 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.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -71,7 +74,8 @@ import dagger.Component;
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.class, BriarAccountModule.class,
AppModule.class, AppModule.class,
AttachmentModule.class AttachmentModule.class,
MediaModule.class
}) })
public interface AndroidComponent public interface AndroidComponent
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons, extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
@@ -86,12 +90,18 @@ public interface AndroidComponent
@DatabaseExecutor @DatabaseExecutor
Executor databaseExecutor(); Executor databaseExecutor();
TransactionManager transactionManager();
MessageTracker messageTracker(); MessageTracker messageTracker();
LifecycleManager lifecycleManager(); LifecycleManager lifecycleManager();
IdentityManager identityManager(); IdentityManager identityManager();
AttachmentReader attachmentReader();
AuthorManager authorManager();
PluginManager pluginManager(); PluginManager pluginManager();
EventBus eventBus(); EventBus eventBus();
@@ -173,8 +183,6 @@ public interface AndroidComponent
void inject(BriarService briarService); void inject(BriarService briarService);
void inject(BriarReportSender briarReportSender);
void inject(NotificationCleanupService notificationCleanupService); void inject(NotificationCleanupService notificationCleanupService);
void inject(EmojiTextInputView textInputView); void inject(EmojiTextInputView textInputView);

View File

@@ -109,7 +109,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Nullable @Nullable
private GroupId blockedGroup = null; private GroupId blockedGroup = null;
private boolean blockSignInReminder = false; private boolean blockSignInReminder = false;
private boolean blockBlogs = false; private boolean blockForums = false, blockGroups = false,
blockBlogs = false;
private long lastSound = 0; private long lastSound = 0;
private volatile Settings settings = new Settings(); private volatile Settings settings = new Settings();
@@ -223,8 +224,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
if (s.getNamespace().equals(SETTINGS_NAMESPACE)) if (s.getNamespace().equals(SETTINGS_NAMESPACE))
settings = s.getSettings(); settings = s.getSettings();
} else if (e instanceof ConversationMessageReceivedEvent) { } else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent p = ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent) e; (ConversationMessageReceivedEvent<?>) e;
showContactNotification(p.getContactId()); showContactNotification(p.getContactId());
} else if (e instanceof GroupMessageAddedEvent) { } else if (e instanceof GroupMessageAddedEvent) {
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
@@ -385,6 +386,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread @UiThread
private void showGroupMessageNotification(GroupId g) { private void showGroupMessageNotification(GroupId g) {
if (blockGroups) return;
if (g.equals(blockedGroup)) return; if (g.equals(blockedGroup)) return;
groupCounts.add(g); groupCounts.add(g);
updateGroupMessageNotification(true); updateGroupMessageNotification(true);
@@ -452,6 +454,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread @UiThread
private void showForumPostNotification(GroupId g) { private void showForumPostNotification(GroupId g) {
if (blockForums) return;
if (g.equals(blockedGroup)) return; if (g.equals(blockedGroup)) return;
forumCounts.add(g); forumCounts.add(g);
updateForumPostNotification(true); updateForumPostNotification(true);
@@ -681,6 +684,26 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}); });
} }
@Override
public void blockAllForumPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockForums = true);
}
@Override
public void unblockAllForumPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockForums = false);
}
@Override
public void blockAllGroupMessageNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockGroups = true);
}
@Override
public void unblockAllGroupMessageNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockGroups = false);
}
@Override @Override
public void blockAllBlogPostNotifications() { public void blockAllBlogPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = true); androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = true);

View File

@@ -27,15 +27,25 @@ import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils; 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.LockManagerImpl;
import org.briarproject.briar.android.account.SetupModule;
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupModule;
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.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.login.LoginModule; import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule; import org.briarproject.briar.android.navdrawer.NavDrawerModule;
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.test.TestAvatarCreatorImpl;
import org.briarproject.briar.android.viewmodel.ViewModelModule; import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager; import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.File; import java.io.File;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@@ -60,10 +70,19 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBL
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = { @Module(includes = {
SetupModule.class,
DozeHelperModule.class,
ContactExchangeModule.class, ContactExchangeModule.class,
LoginModule.class, LoginModule.class,
NavDrawerModule.class, NavDrawerModule.class,
ViewModelModule.class ViewModelModule.class,
SettingsModule.class,
DevReportModule.class,
ContactListModule.class,
BluetoothSetupModule.class,
// below need to be within same scope as ViewModelProvider.Factory
ForumModule.BindsModule.class,
GroupListModule.class,
}) })
public class AppModule { public class AppModule {
@@ -175,6 +194,12 @@ public class AppModule {
return devConfig; return devConfig;
} }
@Provides
TestAvatarCreator provideTestAvatarCreator(
TestAvatarCreatorImpl testAvatarCreator) {
return testAvatarCreator;
}
@Provides @Provides
SharedPreferences provideSharedPreferences(Application app) { SharedPreferences provideSharedPreferences(Application app) {
// FIXME unify this with getDefaultSharedPreferences() // FIXME unify this with getDefaultSharedPreferences()
@@ -196,7 +221,10 @@ public class AppModule {
ScreenFilterMonitor provideScreenFilterMonitor( ScreenFilterMonitor provideScreenFilterMonitor(
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
ScreenFilterMonitorImpl screenFilterMonitor) { ScreenFilterMonitorImpl screenFilterMonitor) {
lifecycleManager.registerService(screenFilterMonitor); if (SDK_INT <= 29) {
// this keeps track of installed apps and does not work on API 30+
lifecycleManager.registerService(screenFilterMonitor);
}
return screenFilterMonitor; return screenFilterMonitor;
} }
@@ -235,6 +263,17 @@ public class AppModule {
@Provides @Provides
FeatureFlags provideFeatureFlags() { 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

@@ -14,19 +14,13 @@ import android.preference.PreferenceManager;
import com.vanniktech.emoji.EmojiManager; import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.google.GoogleEmojiProvider; import com.vanniktech.emoji.google.GoogleEmojiProvider;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
import org.briarproject.bramble.BrambleAndroidEagerSingletons; import org.briarproject.bramble.BrambleAndroidEagerSingletons;
import org.briarproject.bramble.BrambleAppComponent; import org.briarproject.bramble.BrambleAppComponent;
import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.CachingLogHandler; import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.reporting.BriarReportPrimer; import org.briarproject.briar.android.reporting.BriarExceptionHandler;
import org.briarproject.briar.android.reporting.BriarReportSenderFactory;
import org.briarproject.briar.android.reporting.DevReportActivity;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import java.util.Collection; import java.util.Collection;
@@ -34,50 +28,14 @@ import java.util.logging.Handler;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.annotation.NonNull;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME;
import static org.acra.ReportField.BRAND;
import static org.acra.ReportField.BUILD_CONFIG;
import static org.acra.ReportField.CRASH_CONFIGURATION;
import static org.acra.ReportField.CUSTOM_DATA;
import static org.acra.ReportField.DEVICE_FEATURES;
import static org.acra.ReportField.DISPLAY;
import static org.acra.ReportField.INITIAL_CONFIGURATION;
import static org.acra.ReportField.PACKAGE_NAME;
import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.PRODUCT;
import static org.acra.ReportField.REPORT_ID;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_APP_START_DATE;
import static org.acra.ReportField.USER_CRASH_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@ReportsCrashes(
reportPrimerClass = BriarReportPrimer.class,
logcatArguments = {"-d", "-v", "time", "*:I"},
reportSenderFactoryClasses = {BriarReportSenderFactory.class},
mode = ReportingInteractionMode.DIALOG,
reportDialogClass = DevReportActivity.class,
resDialogOkToast = R.string.dev_report_saved,
deleteOldUnsentReportsOnApplicationStart = false,
buildConfigClass = BuildConfig.class,
customReportContent = {
REPORT_ID,
APP_VERSION_CODE, APP_VERSION_NAME, PACKAGE_NAME,
PHONE_MODEL, ANDROID_VERSION, BRAND, PRODUCT,
BUILD_CONFIG,
CUSTOM_DATA,
STACK_TRACE,
INITIAL_CONFIGURATION, CRASH_CONFIGURATION,
DISPLAY, DEVICE_FEATURES,
USER_APP_START_DATE, USER_CRASH_DATE
}
)
public class BriarApplicationImpl extends Application public class BriarApplicationImpl extends Application
implements BriarApplication { implements BriarApplication {
@@ -85,12 +43,15 @@ public class BriarApplicationImpl extends Application
getLogger(BriarApplicationImpl.class.getName()); getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler(); private final CachingLogHandler logHandler = new CachingLogHandler();
private final BriarExceptionHandler exceptionHandler =
new BriarExceptionHandler(this);
private AndroidComponent applicationComponent; private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs; private volatile SharedPreferences prefs;
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
if (prefs == null) if (prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(base); prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here. // Loading the language needs to be done here.
@@ -98,7 +59,6 @@ public class BriarApplicationImpl extends Application
super.attachBaseContext( super.attachBaseContext(
Localizer.getInstance().setLocale(base)); Localizer.getInstance().setLocale(base));
setTheme(base, prefs); setTheme(base, prefs);
ACRA.init(this);
} }
@Override @Override
@@ -144,7 +104,7 @@ public class BriarApplicationImpl extends Application
} }
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
Localizer.getInstance().setLocale(this); Localizer.getInstance().setLocale(this);
} }

View File

@@ -14,6 +14,8 @@ import android.content.ServiceConnection;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import com.bumptech.glide.Glide;
import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -246,10 +248,13 @@ public class BriarService extends Service {
LOG.info("Trim memory: near end of LRU list"); LOG.info("Trim memory: near end of LRU list");
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) { } else if (level == TRIM_MEMORY_RUNNING_MODERATE) {
LOG.info("Trim memory: running moderately low"); LOG.info("Trim memory: running moderately low");
Glide.get(getApplicationContext()).clearMemory();
} else if (level == TRIM_MEMORY_RUNNING_LOW) { } else if (level == TRIM_MEMORY_RUNNING_LOW) {
LOG.info("Trim memory: running low"); LOG.info("Trim memory: running low");
// TODO investigate if we can clear Glide cache here as well
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) { } else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
LOG.warning("Trim memory: running critically low"); 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 we're not in the foreground, clear the UI to save memory
if (app.isRunningInBackground()) hideUi(); if (app.isRunningInBackground()) hideUi();
} else if (LOG.isLoggable(INFO)) { } else if (LOG.isLoggable(INFO)) {

View File

@@ -58,7 +58,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
Logger.getLogger(ScreenFilterMonitorImpl.class.getName()); Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
/* /*
* Ignore Play Services if it uses this package name and public key - it's * Ignore Play Services if it uses this package name and public key - it's
* effectively a system app, but not flagged as such on older systems * effectively a system app, but not flagged as such on older systems
*/ */
private static final String PLAY_SERVICES_PACKAGE = private static final String PLAY_SERVICES_PACKAGE =
@@ -108,7 +108,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED, Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet()); Collections.emptySet());
List<AppDetails> apps = new ArrayList<>(); List<AppDetails> apps = new ArrayList<>();
List<PackageInfo> packageInfos = @SuppressLint("QueryPermissionsNeeded") List<PackageInfo> packageInfos =
pm.getInstalledPackages(GET_PERMISSIONS); pm.getInstalledPackages(GET_PERMISSIONS);
for (PackageInfo packageInfo : packageInfos) { for (PackageInfo packageInfo : packageInfos) {
if (!allowed.contains(packageInfo.packageName) if (!allowed.contains(packageInfo.packageName)

View File

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

View File

@@ -104,9 +104,15 @@ public class DozeFragment extends SetupFragment
@Override @Override
public void onClick(View view) { public void onClick(View view) {
next.setVisibility(INVISIBLE); setNextClicked();
progressBar.setVisibility(VISIBLE); viewModel.dozeExceptionConfirmed();
setupController.createAccount();
} }
@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 @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class SetPasswordFragment extends SetupFragment { public class SetPasswordFragment extends SetupFragment {
private final static String TAG = SetPasswordFragment.class.getName(); private final static String TAG = SetPasswordFragment.class.getName();
private TextInputLayout passwordEntryWrapper; private TextInputLayout passwordEntryWrapper;
@@ -56,7 +55,7 @@ public class SetPasswordFragment extends SetupFragment {
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_password_intro)); requireActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container, View v = inflater.inflate(R.layout.fragment_setup_password, container,
false); false);
strengthMeter = v.findViewById(R.id.strength_meter); strengthMeter = v.findViewById(R.id.strength_meter);
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper); passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
@@ -71,7 +70,7 @@ public class SetPasswordFragment extends SetupFragment {
passwordConfirmation.addTextChangedListener(this); passwordConfirmation.addTextChangedListener(this);
nextButton.setOnClickListener(this); nextButton.setOnClickListener(this);
if (!setupController.needToShowDozeFragment()) { if (!viewModel.needToShowDozeFragment()) {
nextButton.setText(R.string.create_account_button); nextButton.setText(R.string.create_account_button);
passwordConfirmation.setImeOptions(IME_ACTION_DONE); passwordConfirmation.setImeOptions(IME_ACTION_DONE);
} }
@@ -97,7 +96,7 @@ public class SetPasswordFragment extends SetupFragment {
strengthMeter strengthMeter
.setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE); .setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE);
float strength = setupController.estimatePasswordStrength(password1); float strength = viewModel.estimatePasswordStrength(password1);
strengthMeter.setStrength(strength); strengthMeter.setStrength(strength);
boolean strongEnough = strength >= QUITE_WEAK; boolean strongEnough = strength >= QUITE_WEAK;
@@ -117,14 +116,20 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken(); IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE); Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0); ((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
setupController.setPassword(passwordEntry.getText().toString());
if (setupController.needToShowDozeFragment()) { setNextClicked();
setupController.showDozeFragment(); viewModel.setPassword(passwordEntry.getText().toString());
} else {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
}
} }
@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.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -15,28 +14,36 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; 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_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; 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.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 @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class SetupActivity extends BaseActivity public class SetupActivity extends BaseActivity
implements BaseFragmentListener { implements BaseFragmentListener {
private static final String STATE_KEY_AUTHOR_NAME = "authorName";
private static final String STATE_KEY_PASSWORD = "password";
@Inject @Inject
AccountManager accountManager; ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel;
@Inject @Override
SetupController setupController; public void injectActivity(ActivityComponent component) {
component.inject(this);
@Nullable viewModel = new ViewModelProvider(this, viewModelFactory)
private String authorName, password; .get(SetupViewModel.class);
viewModel.getState().observeEvent(this, this::onStateChanged);
}
@Override @Override
public void onCreate(@Nullable Bundle state) { public void onCreate(@Nullable Bundle state) {
@@ -44,58 +51,27 @@ public class SetupActivity extends BaseActivity
// fade-in after splash screen instead of default animation // fade-in after splash screen instead of default animation
overridePendingTransition(R.anim.fade_in, R.anim.fade_out); overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
setContentView(R.layout.activity_fragment_container); setContentView(R.layout.activity_fragment_container);
}
if (state == null) { private void onStateChanged(SetupViewModel.State state) {
if (accountManager.accountExists()) throw new AssertionError(); if (state == AUTHOR_NAME) {
showInitialFragment(AuthorNameFragment.newInstance()); showInitialFragment(AuthorNameFragment.newInstance());
} else { } else if (state == SET_PASSWORD) {
authorName = state.getString(STATE_KEY_AUTHOR_NAME); showPasswordFragment();
password = state.getString(STATE_KEY_PASSWORD); } 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() { void showPasswordFragment() {
if (authorName == null) throw new IllegalStateException();
showNextFragment(SetPasswordFragment.newInstance()); showNextFragment(SetPasswordFragment.newInstance());
} }
@TargetApi(23) @TargetApi(23)
void showDozeFragment() { void showDozeFragment() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
showNextFragment(DozeFragment.newInstance()); 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; package org.briarproject.briar.android.account;
import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
@@ -17,7 +19,10 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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_DONE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT; 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, abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener { OnEditorActionListener, OnClickListener {
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
private boolean clicked = false;
@Inject @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 @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 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

@@ -20,8 +20,11 @@ import org.briarproject.briar.android.blog.ReblogFragment;
import org.briarproject.briar.android.blog.RssFeedImportActivity; import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupActivity;
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupChooseFragment;
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupPendingFragment;
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupStartFragment;
import org.briarproject.briar.android.contact.ContactListFragment; 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.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment; import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment; import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
@@ -60,12 +63,15 @@ import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity; import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationModule; import org.briarproject.briar.android.privategroup.invitation.GroupInvitationModule;
import org.briarproject.briar.android.privategroup.list.GroupListFragment; import org.briarproject.briar.android.privategroup.list.GroupListFragment;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity; import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity;
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule; import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule; import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity; import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment; 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.SettingsActivity;
import org.briarproject.briar.android.settings.SettingsFragment; import org.briarproject.briar.android.settings.SettingsFragment;
import org.briarproject.briar.android.sharing.BlogInvitationActivity; import org.briarproject.briar.android.sharing.BlogInvitationActivity;
@@ -86,12 +92,10 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
ActivityModule.class, ActivityModule.class,
BlogModule.class, BlogModule.class,
ContactModule.class,
CreateGroupModule.class, CreateGroupModule.class,
ForumModule.class, ForumModule.class,
GroupInvitationModule.class, GroupInvitationModule.class,
GroupConversationModule.class, GroupConversationModule.class,
GroupListModule.class,
GroupMemberModule.class, GroupMemberModule.class,
GroupRevealModule.class, GroupRevealModule.class,
SharingModule.class SharingModule.class
@@ -184,6 +188,10 @@ public interface ActivityComponent {
void inject(PendingContactListActivity activity); void inject(PendingContactListActivity activity);
void inject(CrashReportActivity crashReportActivity);
void inject(BluetoothSetupActivity activity);
// Fragments // Fragments
void inject(AuthorNameFragment fragment); void inject(AuthorNameFragment fragment);
@@ -234,4 +242,16 @@ public interface ActivityComponent {
void inject(ImageFragment imageFragment); void inject(ImageFragment imageFragment);
void inject(ReportFormFragment reportFormFragment);
void inject(CrashFragment crashFragment);
void inject(ConfirmAvatarDialogFragment fragment);
void inject(BluetoothSetupStartFragment fragment);
void inject(BluetoothSetupChooseFragment fragment);
void inject(BluetoothSetupPendingFragment fragment);
} }

View File

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

View File

@@ -6,7 +6,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -18,7 +17,6 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment; import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.reporting.DevReportActivity;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.widget.TapSafeFrameLayout; import org.briarproject.briar.android.widget.TapSafeFrameLayout;
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener; import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
@@ -40,9 +38,11 @@ import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static androidx.lifecycle.Lifecycle.State.STARTED; import static androidx.lifecycle.Lifecycle.State.STARTED;
import static java.util.Collections.emptyList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS; import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
@@ -50,7 +50,6 @@ import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
/** /**
* Warning: Some activities don't extend {@link BaseActivity}. * Warning: Some activities don't extend {@link BaseActivity}.
* E.g. {@link DevReportActivity}
*/ */
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -123,6 +122,7 @@ public abstract class BaseActivity extends AppCompatActivity
return new ActivityModule(this); return new ActivityModule(this);
} }
// TODO use a test module where this is used in tests
protected ForumModule getForumModule() { protected ForumModule getForumModule() {
return new ForumModule(); return new ForumModule();
} }
@@ -202,9 +202,15 @@ public abstract class BaseActivity extends AppCompatActivity
// If the dialog is already visible, filter the tap // If the dialog is already visible, filter the tap
ScreenFilterDialogFragment f = findDialogFragment(); ScreenFilterDialogFragment f = findDialogFragment();
if (f != null && f.isVisible()) return false; if (f != null && f.isVisible()) return false;
Collection<AppDetails> apps = screenFilterMonitor.getApps(); Collection<AppDetails> apps;
// If all overlay apps have been allowed, allow the tap // querying all apps is only possible at API 29 and below
if (apps.isEmpty()) return true; if (SDK_INT <= 29) {
apps = screenFilterMonitor.getApps();
// If all overlay apps have been allowed, allow the tap
if (apps.isEmpty()) return true;
} else {
apps = emptyList();
}
// Show dialog unless onSaveInstanceState() has been called, see #1112 // Show dialog unless onSaveInstanceState() has been called, see #1112
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (!fm.isStateSaved()) { if (!fm.isStateSaved()) {
@@ -241,7 +247,7 @@ public abstract class BaseActivity extends AppCompatActivity
} }
@UiThread @UiThread
public void handleDbException(DbException e) { public void handleException(Exception e) {
supportFinishAfterTransition(); supportFinishAfterTransition();
} }
@@ -266,7 +272,12 @@ public abstract class BaseActivity extends AppCompatActivity
private void protectToolbar() { private void protectToolbar() {
findToolbar(); findToolbar();
if (toolbar != null) { if (toolbar != null) {
boolean filter = !screenFilterMonitor.getApps().isEmpty(); boolean filter;
if (SDK_INT <= 29) {
filter = !screenFilterMonitor.getApps().isEmpty();
} else {
filter = true;
}
UiUtils.setFilterTouchesWhenObscured(toolbar, filter); UiUtils.setFilterTouchesWhenObscured(toolbar, filter);
} }
} }

View File

@@ -129,10 +129,6 @@ public abstract class BriarActivity extends BaseActivity {
lockManager.onActivityStop(); lockManager.onActivityStop();
} }
protected boolean signedIn() {
return briarController.accountSignedIn();
}
/** /**
* Sets the transition animations. * Sets the transition animations.
* *

View File

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

View File

@@ -1,33 +1,24 @@
package org.briarproject.briar.android.attachment; package org.briarproject.briar.android.attachment;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
import android.net.Uri; import android.net.Uri;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; 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.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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.annotation.Nullable; 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.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes; 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.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault @NotNullByDefault
class AttachmentCreationTask { class AttachmentCreationTask {
private static Logger LOG = private static final Logger LOG =
getLogger(AttachmentCreationTask.class.getName()); getLogger(AttachmentCreationTask.class.getName());
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
private final ImageSizeCalculator imageSizeCalculator; private final ImageCompressor imageCompressor;
private final GroupId groupId; private final GroupId groupId;
private final Collection<Uri> uris; private final Collection<Uri> uris;
private final boolean needsSize; private final boolean needsSize;
@@ -59,11 +47,11 @@ class AttachmentCreationTask {
AttachmentCreationTask(MessagingManager messagingManager, AttachmentCreationTask(MessagingManager messagingManager,
ContentResolver contentResolver, ContentResolver contentResolver,
AttachmentCreator attachmentCreator, AttachmentCreator attachmentCreator,
ImageSizeCalculator imageSizeCalculator, ImageCompressor imageCompressor,
GroupId groupId, Collection<Uri> uris, boolean needsSize) { GroupId groupId, Collection<Uri> uris, boolean needsSize) {
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
this.imageSizeCalculator = imageSizeCalculator; this.imageCompressor = imageCompressor;
this.groupId = groupId; this.groupId = groupId;
this.uris = uris; this.uris = uris;
this.needsSize = needsSize; this.needsSize = needsSize;
@@ -110,66 +98,19 @@ class AttachmentCreationTask {
String contentType = contentResolver.getType(uri); String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type"); if (contentType == null) throw new IOException("null content type");
if (!asList(getSupportedImageContentTypes()).contains(contentType)) { if (!asList(getSupportedImageContentTypes()).contains(contentType)) {
String uriString = uri.toString(); throw new UnsupportedMimeTypeException(contentType, uri);
throw new UnsupportedMimeTypeException("", contentType, uriString);
} }
InputStream is = contentResolver.openInputStream(uri); InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException(); if (is == null) throw new IOException();
is = compressImage(is, contentType); is = imageCompressor
contentType = "image/jpeg"; .compressImage(is, contentType);
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is); .addLocalAttachment(groupId, timestamp,
ImageCompressor.MIME_TYPE, is);
tryToClose(is, LOG, WARNING); tryToClose(is, LOG, WARNING);
logDuration(LOG, "Storing attachment", start); logDuration(LOG, "Storing attachment", start);
return h; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.messaging.FileTooBigException; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; 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.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR; import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; 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 @NotNullByDefault
class AttachmentCreatorImpl implements AttachmentCreator { class AttachmentCreatorImpl implements AttachmentCreator {
private static Logger LOG = private static final Logger LOG =
getLogger(AttachmentCreatorImpl.class.getName()); getLogger(AttachmentCreatorImpl.class.getName());
private final Application app; private final Application app;
@@ -49,7 +49,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
private final Executor ioExecutor; private final Executor ioExecutor;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final AttachmentRetriever retriever; private final AttachmentRetriever retriever;
private final ImageSizeCalculator imageSizeCalculator; private final ImageCompressor imageCompressor;
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults = private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
@@ -64,12 +64,12 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@Inject @Inject
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor, AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
MessagingManager messagingManager, AttachmentRetriever retriever, MessagingManager messagingManager, AttachmentRetriever retriever,
ImageSizeCalculator imageSizeCalculator) { ImageCompressor imageCompressor) {
this.app = app; this.app = app;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.retriever = retriever; this.retriever = retriever;
this.imageSizeCalculator = imageSizeCalculator; this.imageCompressor = imageCompressor;
} }
@Override @Override
@@ -89,7 +89,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
if (id == null) throw new IllegalStateException(); if (id == null) throw new IllegalStateException();
boolean needsSize = uris.size() == 1; boolean needsSize = uris.size() == 1;
task = new AttachmentCreationTask(messagingManager, task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, imageSizeCalculator, id, app.getContentResolver(), this, imageCompressor, id,
uris, needsSize); uris, needsSize);
ioExecutor.execute(() -> task.storeAttachments()); ioExecutor.execute(() -> task.storeAttachments());
}); });

View File

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

View File

@@ -3,7 +3,7 @@ package org.briarproject.briar.android.attachment;
import android.net.Uri; import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.Collection;
import java.util.List; import java.util.List;

View File

@@ -12,16 +12,6 @@ import static org.briarproject.briar.android.attachment.AttachmentDimensions.get
@Module @Module
public class AttachmentModule { public class AttachmentModule {
@Provides
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
return imageHelper;
}
@Provides
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
return new ImageSizeCalculator(imageHelper);
}
@Provides @Provides
AttachmentDimensions provideAttachmentDimensions(Application app) { AttachmentDimensions provideAttachmentDimensions(Application app) {
return getAttachmentDimensions(app.getResources()); 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.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent; import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
@@ -49,10 +49,10 @@ public interface AttachmentRetriever {
* Loads an {@link AttachmentItem} * Loads an {@link AttachmentItem}
* that arrived via an {@link AttachmentReceivedEvent} * that arrived via an {@link AttachmentReceivedEvent}
* and notifies the associated {@link LiveData}. * and notifies the associated {@link LiveData}.
* * <p>
* Note that you need to call {@link #getAttachmentItems(PrivateMessageHeader)} * Note that you need to call {@link #getAttachmentItems(PrivateMessageHeader)}
* first to get the LiveData. * first to get the LiveData.
* * <p>
* It is possible that no LiveData is available, * It is possible that no LiveData is available,
* because the message of the AttachmentItem did not arrive, yet. * because the message of the AttachmentItem did not arrive, yet.
* In this case, the load wil be deferred until the message arrives. * 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem.State; import org.briarproject.briar.android.attachment.AttachmentItem.State;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.android.attachment.media.ImageHelper;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.android.attachment.media.ImageSizeCalculator;
import org.briarproject.briar.api.messaging.MessagingManager; 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 org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
@@ -43,7 +46,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor @DatabaseExecutor
private final Executor dbExecutor; private final Executor dbExecutor;
private final MessagingManager messagingManager; private final AttachmentReader attachmentReader;
private final ImageHelper imageHelper; private final ImageHelper imageHelper;
private final ImageSizeCalculator imageSizeCalculator; private final ImageSizeCalculator imageSizeCalculator;
private final int defaultSize; private final int defaultSize;
@@ -57,11 +60,10 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@Inject @Inject
AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor, AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor,
MessagingManager messagingManager, AttachmentReader attachmentReader, AttachmentDimensions dimensions,
AttachmentDimensions dimensions, ImageHelper imageHelper, ImageHelper imageHelper, ImageSizeCalculator imageSizeCalculator) {
ImageSizeCalculator imageSizeCalculator) {
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.messagingManager = messagingManager; this.attachmentReader = attachmentReader;
this.imageHelper = imageHelper; this.imageHelper = imageHelper;
this.imageSizeCalculator = imageSizeCalculator; this.imageSizeCalculator = imageSizeCalculator;
defaultSize = dimensions.defaultSize; defaultSize = dimensions.defaultSize;
@@ -75,7 +77,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor @DatabaseExecutor
public Attachment getMessageAttachment(AttachmentHeader h) public Attachment getMessageAttachment(AttachmentHeader h)
throws DbException { throws DbException {
return messagingManager.getAttachment(h); return attachmentReader.getAttachment(h);
} }
@Override @Override
@@ -86,13 +88,11 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
boolean needsSize = headers.size() == 1; boolean needsSize = headers.size() == 1;
for (AttachmentHeader h : headers) { for (AttachmentHeader h : headers) {
// try cache for existing item live data // try cache for existing item live data
MutableLiveData<AttachmentItem> liveData; MutableLiveData<AttachmentItem> liveData =
if (needsSize) liveData = itemsWithSize.get(h.getMessageId()); itemsWithSize.get(h.getMessageId());
else { if (!needsSize && liveData == null) {
// try items with size first, as they work as well // check cache for items that don't need the size
liveData = itemsWithSize.get(h.getMessageId()); liveData = itemsWithoutSize.get(h.getMessageId());
if (liveData == null)
liveData = itemsWithoutSize.get(h.getMessageId());
} }
// create new live data with LOADING item if cache miss // 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 a live data is already cached we don't need to do anything
if (itemsWithSize.containsKey(h.getMessageId())) return; if (itemsWithSize.containsKey(h.getMessageId())) return;
try { try {
Attachment a = messagingManager.getAttachment(h); Attachment a = attachmentReader.getAttachment(h);
AttachmentItem item = createAttachmentItem(a, true); AttachmentItem item = createAttachmentItem(a, true);
MutableLiveData<AttachmentItem> liveData = MutableLiveData<AttachmentItem> liveData =
new MutableLiveData<>(item); new MutableLiveData<>(item);
@@ -173,7 +173,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
Attachment a; Attachment a;
AttachmentItem item; AttachmentItem item;
try { try {
a = messagingManager.getAttachment(h); a = attachmentReader.getAttachment(h);
item = createAttachmentItem(a, needsSize); item = createAttachmentItem(a, needsSize);
} catch (NoSuchMessageException e) { } catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet"); LOG.info("Attachment not received yet");
@@ -210,26 +210,30 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) { private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) {
// calculate thumbnail size // calculate thumbnail size
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType); Size thumbnailSize =
if (!size.error) { new Size(defaultSize, defaultSize, size.getMimeType());
if (!size.hasError()) {
thumbnailSize = thumbnailSize =
getThumbnailSize(size.width, size.height, size.mimeType); getThumbnailSize(size.getWidth(), size.getHeight(),
size.getMimeType());
} }
// get file extension // get file extension
String extension = imageHelper.getExtensionFromMimeType(size.mimeType); String extension =
boolean hasError = extension == null || size.error; imageHelper.getExtensionFromMimeType(size.getMimeType());
if (!h.getContentType().equals(size.mimeType)) { boolean hasError = extension == null || size.hasError();
if (!h.getContentType().equals(size.getMimeType())) {
if (LOG.isLoggable(WARNING)) { if (LOG.isLoggable(WARNING)) {
LOG.warning("Header has different mime type (" + LOG.warning("Header has different mime type (" +
h.getContentType() + ") than image (" + size.mimeType + h.getContentType() + ") than image (" +
")."); size.getMimeType() + ").");
} }
hasError = true; hasError = true;
} }
if (extension == null) extension = ""; if (extension == null) extension = "";
State state = hasError ? ERROR : AVAILABLE; State state = hasError ? ERROR : AVAILABLE;
return new AttachmentItem(h, size.width, size.height, return new AttachmentItem(h, size.getWidth(), size.getHeight(),
extension, thumbnailSize.width, thumbnailSize.height, state); extension, thumbnailSize.getWidth(), thumbnailSize.getHeight(),
state);
} }
private Size getThumbnailSize(int width, int height, String mimeType) { 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; 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.graphics.BitmapFactory;
import android.webkit.MimeTypeMap; 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 com.bumptech.glide.util.MarkEnforcingInputStream;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -23,20 +23,21 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
class ImageSizeCalculator { class ImageSizeCalculatorImpl implements ImageSizeCalculator {
private static final Logger LOG = private static final Logger LOG =
getLogger(ImageSizeCalculator.class.getName()); getLogger(ImageSizeCalculatorImpl.class.getName());
private static final int READ_LIMIT = 1024 * 8192; private static final int READ_LIMIT = 1024 * 8192;
private final ImageHelper imageHelper; private final ImageHelper imageHelper;
ImageSizeCalculator(ImageHelper imageHelper) { ImageSizeCalculatorImpl(ImageHelper imageHelper) {
this.imageHelper = imageHelper; this.imageHelper = imageHelper;
} }
Size getSize(InputStream is, String contentType) { @Override
public Size getSize(InputStream is, String contentType) {
Size size = new Size(); Size size = new Size();
is = new MarkEnforcingInputStream(is); is = new MarkEnforcingInputStream(is);
is.mark(READ_LIMIT); is.mark(READ_LIMIT);
@@ -49,7 +50,7 @@ class ImageSizeCalculator {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
if (size.error) { if (size.hasError()) {
// need to mark again to re-add read limit // need to mark again to re-add read limit
is.mark(READ_LIMIT); is.mark(READ_LIMIT);
try { 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

@@ -232,7 +232,7 @@ public class BlogFragment extends BaseFragment
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
} }
); );
@@ -277,7 +277,7 @@ public class BlogFragment extends BaseFragment
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -296,7 +296,7 @@ public class BlogFragment extends BaseFragment
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -318,7 +318,7 @@ public class BlogFragment extends BaseFragment
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -398,7 +398,7 @@ public class BlogFragment extends BaseFragment
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }

View File

@@ -58,7 +58,7 @@ public class BlogPostFragment extends BasePostFragment implements BlogListener {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.blog; package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.identity.Author; 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.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.blog.BlogPostHeader; import org.briarproject.briar.api.blog.BlogPostHeader;

View File

@@ -156,7 +156,7 @@ public class FeedFragment extends BaseFragment implements
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -187,7 +187,7 @@ public class FeedFragment extends BaseFragment implements
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -242,7 +242,7 @@ public class FeedFragment extends BaseFragment implements
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
} }
); );

View File

@@ -79,7 +79,7 @@ public class FeedPostFragment extends BasePostFragment {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }

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.TextInputView;
import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List; import java.util.List;
@@ -101,7 +101,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
@@ -128,7 +128,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
new UiExceptionHandler<DbException>(this) { new UiExceptionHandler<DbException>(this) {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
finish(); finish();
@@ -156,16 +156,16 @@ public class ReblogFragment extends BaseFragment implements SendListener {
progressBar = v.findViewById(R.id.progressBar); progressBar = v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout), post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() { true, new OnBlogPostClickListener() {
@Override @Override
public void onBlogPostClick(BlogPostItem post) { public void onBlogPostClick(BlogPostItem post) {
// do nothing // do nothing
} }
@Override @Override
public void onAuthorClick(BlogPostItem post) { public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here // probably don't want to allow author clicks here
} }
}, getFragmentManager()); }, getFragmentManager());
input = v.findViewById(R.id.inputText); 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;
import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.android.AndroidNotificationManager; 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.BlogManager;
import org.briarproject.briar.api.blog.BlogPost; import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.List; import java.util.List;

View File

@@ -0,0 +1,60 @@
package org.briarproject.briar.android.bluetoothsetup;
import android.os.Bundle;
import android.view.MenuItem;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.appcompat.app.ActionBar;
import androidx.lifecycle.ViewModelProvider;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BluetoothSetupActivity extends BriarActivity implements
BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private BluetoothSetupViewModel viewModel;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_fragment_container);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(BluetoothSetupViewModel.class);
if (state == null) {
showInitialFragment(new BluetoothSetupStartFragment());
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,72 @@
package org.briarproject.briar.android.bluetoothsetup;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.ViewModelProvider;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BluetoothSetupChooseFragment extends BaseFragment {
private static final String TAG =
BluetoothSetupChooseFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private BluetoothSetupViewModel viewModel;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
viewModel = new ViewModelProvider(requireActivity())
.get(BluetoothSetupViewModel.class);
View v =
inflater.inflate(R.layout.fragment_bluetooth_setup_choose,
container, false);
// TODO to enable when user picks a device from list
Button continueButton = v.findViewById(R.id.continueButton);
continueButton.setOnClickListener(view -> {
showNextFragment(new BluetoothSetupPendingFragment());
});
continueButton.setEnabled(true);
// RecyclerView devices = v.findViewById(R.id.devices);
// devices.setHasFixedSize(true);
// final LinearLayoutManager layoutManager =
// new LinearLayoutManager(getActivity(),
// LinearLayoutManager.VERTICAL, false);
// devices.setLayoutManager(layoutManager);
return v;
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.briar.android.bluetoothsetup;
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 BluetoothSetupModule {
@Binds
@IntoMap
@ViewModelKey(BluetoothSetupViewModel.class)
abstract ViewModel bindBluetoothSetupViewModel(
BluetoothSetupViewModel bluetoothSetupViewModel);
}

View File

@@ -0,0 +1,59 @@
package org.briarproject.briar.android.bluetoothsetup;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.ViewModelProvider;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BluetoothSetupPendingFragment extends BaseFragment {
private static final String TAG =
BluetoothSetupPendingFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private BluetoothSetupViewModel viewModel;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
viewModel = new ViewModelProvider(requireActivity())
.get(BluetoothSetupViewModel.class);
View v = inflater.inflate(R.layout.fragment_bluetooth_setup_pending,
container, false);
return v;
}
private void onContinueButtonClicked() {
}
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.briar.android.bluetoothsetup;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.ViewModelProvider;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BluetoothSetupStartFragment extends BaseFragment {
private static final String TAG =
BluetoothSetupStartFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private BluetoothSetupViewModel viewModel;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
viewModel = new ViewModelProvider(requireActivity())
.get(BluetoothSetupViewModel.class);
View v = inflater.inflate(R.layout.fragment_bluetooth_setup_start,
container, false);
// TODO device-BT and BT-plugin needs to be enabled at this point
Button startButton = v.findViewById(R.id.startButton);
startButton.setOnClickListener(view -> {
showNextFragment(new BluetoothSetupChooseFragment());
});
startButton.setEnabled(true);
return v;
}
}

View File

@@ -0,0 +1,33 @@
package org.briarproject.briar.android.bluetoothsetup;
import android.app.Application;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
@NotNullByDefault
public class BluetoothSetupViewModel extends DbViewModel {
private final static Logger LOG =
getLogger(BluetoothSetupViewModel.class.getName());
@Inject
BluetoothSetupViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
}
}

View File

@@ -3,14 +3,12 @@ package org.briarproject.briar.android.contact;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.briar.android.util.BriarAdapter; import org.briarproject.briar.android.util.BriarAdapter;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
public abstract class BaseContactListAdapter<I extends ContactItem, VH extends ContactItemViewHolder<I>> 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; 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> { public interface OnContactClickListener<I> {
void onItemClick(View view, I item); 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.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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 @NotNullByDefault
public class ContactItem { public class ContactItem {
private final Contact contact; private final Contact contact;
private boolean connected; private final AuthorInfo authorInfo;
private final boolean connected;
public ContactItem(Contact contact) { public ContactItem(Contact contact, AuthorInfo authorInfo) {
this(contact, false); this(contact, authorInfo, false);
} }
public ContactItem(Contact contact, boolean connected) { public ContactItem(Contact contact, AuthorInfo authorInfo,
boolean connected) {
this.contact = contact; this.contact = contact;
this.authorInfo = authorInfo;
this.connected = connected; this.connected = connected;
} }
@@ -25,12 +29,12 @@ public class ContactItem {
return contact; return contact;
} }
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
boolean isConnected() { boolean isConnected() {
return connected; 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.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -14,9 +13,9 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView; 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.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -40,9 +39,7 @@ public class ContactItemViewHolder<I extends ContactItem>
} }
protected void bind(I item, @Nullable OnContactClickListener<I> listener) { protected void bind(I item, @Nullable OnContactClickListener<I> listener) {
Author author = item.getContact().getAuthor(); setAvatar(avatar, item);
avatar.setImageDrawable(
new IdenticonDrawable(author.getId().getBytes()));
name.setText(getContactDisplayName(item.getContact())); name.setText(getContactDisplayName(item.getContact()));
if (bulb != null) { if (bulb != null) {

View File

@@ -1,50 +1,73 @@
package org.briarproject.briar.android.contact; package org.briarproject.briar.android.contact;
import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import org.briarproject.briar.R; 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 @NotNullByDefault
public class ContactListAdapter extends 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) { 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 @Override
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup, public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
int i) { int viewType) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate( View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_contact, viewGroup, false); R.layout.list_item_contact, viewGroup, false);
return new ContactListItemViewHolder(v); return new ContactListItemViewHolder(v);
} }
@Override @Override
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) { public void onBindViewHolder(ContactListItemViewHolder viewHolder,
// check for all properties that influence visual int position) {
// representation of contact viewHolder.bind(getItem(position), listener);
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();
} }
@Override
public int compare(ContactListItem c1, ContactListItem c2) {
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
}
} }

View File

@@ -10,25 +10,12 @@ import android.widget.TextView;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.bluetoothsetup.BluetoothSetupActivity;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity; import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
@@ -37,55 +24,35 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.core.app.ActivityCompat; import androidx.lifecycle.ViewModelProvider;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial; import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener; import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static androidx.core.view.ViewCompat.getTransitionName;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE; import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
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.android.conversation.ConversationActivity.CONTACT_ID; import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ContactListFragment extends BaseFragment implements EventListener, public class ContactListFragment extends BaseFragment
OnMenuItemClickListener { implements OnMenuItemClickListener,
OnContactClickListener<ContactListItem> {
public static final String TAG = ContactListFragment.class.getName(); public static final String TAG = ContactListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Inject @Inject
ConnectionRegistry connectionRegistry; ViewModelProvider.Factory viewModelFactory;
@Inject
EventBus eventBus;
@Inject
AndroidNotificationManager notificationManager;
private ContactListAdapter adapter; private ContactListViewModel viewModel;
private final ContactListAdapter adapter = new ContactListAdapter(this);
private BriarRecyclerView list; private BriarRecyclerView list;
/** /**
* The Snackbar is non-null when shown and null otherwise. * The Snackbar is non-null when shown and null otherwise.
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact. * Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
@@ -93,12 +60,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
@Nullable @Nullable
private Snackbar snackbar = null; private Snackbar snackbar = null;
// Fields that are accessed from background threads must be volatile
@Inject
volatile ContactManager contactManager;
@Inject
volatile ConversationManager conversationManager;
public static ContactListFragment newInstance() { public static ContactListFragment newInstance() {
Bundle args = new Bundle(); Bundle args = new Bundle();
ContactListFragment fragment = new ContactListFragment(); ContactListFragment fragment = new ContactListFragment();
@@ -114,6 +75,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
@Override @Override
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
component.inject(this); component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ContactListViewModel.class);
} }
@Nullable @Nullable
@@ -129,37 +92,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial); FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
speedDial.addOnMenuItemClickListener(this); speedDial.addOnMenuItemClickListener(this);
OnContactClickListener<ContactListItem> onContactClickListener =
(view, item) -> {
Intent i = new Intent(getActivity(),
ConversationActivity.class);
ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt());
if (SDK_INT >= 23 && !isSamsung7()) {
ContactListItemViewHolder holder =
(ContactListItemViewHolder) list
.getRecyclerView()
.findViewHolderForAdapterPosition(
adapter.findItemPosition(item));
Pair<View, String> avatar =
Pair.create(holder.avatar,
getTransitionName(holder.avatar));
Pair<View, String> bulb =
Pair.create(holder.bulb,
getTransitionName(holder.bulb));
ActivityOptionsCompat options =
makeSceneTransitionAnimation(getActivity(),
avatar, bulb);
ActivityCompat.startActivity(getActivity(), i,
options.toBundle());
} else {
// work-around for android bug #224270
startActivity(i);
}
};
adapter = new ContactListAdapter(requireContext(),
onContactClickListener);
list = contentView.findViewById(R.id.list); list = contentView.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(requireContext())); list.setLayoutManager(new LinearLayoutManager(requireContext()));
list.setAdapter(adapter); list.setAdapter(adapter);
@@ -167,9 +99,30 @@ public class ContactListFragment extends BaseFragment implements EventListener,
list.setEmptyText(getString(R.string.no_contacts)); list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyAction(getString(R.string.no_contacts_action)); list.setEmptyAction(getString(R.string.no_contacts_action));
viewModel.getContactListItems()
.observe(getViewLifecycleOwner(), result -> {
result.onError(this::handleException).onSuccess(items -> {
adapter.submitList(items);
if (requireNonNull(items).size() == 0) list.showData();
});
});
viewModel.getHasPendingContacts()
.observe(getViewLifecycleOwner(), hasPending -> {
if (hasPending) showSnackBar();
else dismissSnackBar();
});
return contentView; return contentView;
} }
@Override
public void onItemClick(View view, ContactListItem item) {
Intent i = new Intent(getActivity(), ConversationActivity.class);
ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt());
startActivity(i);
}
@Override @Override
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v, public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
int itemId) { int itemId) {
@@ -182,137 +135,31 @@ public class ContactListFragment extends BaseFragment implements EventListener,
case R.id.action_add_contact_remotely: case R.id.action_add_contact_remotely:
startActivity( startActivity(
new Intent(getContext(), AddContactActivity.class)); new Intent(getContext(), AddContactActivity.class));
return;
case R.id.action_bluetooth_setup:
startActivity(
new Intent(getContext(),
BluetoothSetupActivity.class));
} }
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
eventBus.addListener(this); viewModel.clearAllContactNotifications();
notificationManager.clearAllContactNotifications(); viewModel.clearAllContactAddedNotifications();
notificationManager.clearAllContactAddedNotifications(); viewModel.loadContacts();
loadContacts(); viewModel.checkForPendingContacts();
checkForPendingContacts();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
private void checkForPendingContacts() {
listener.runOnDbThread(() -> {
try {
if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(this::dismissSnackBar);
} else {
runOnUiThreadUnlessDestroyed(this::showSnackBar);
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
eventBus.removeListener(this);
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
dismissSnackBar(); dismissSnackBar();
} }
private void loadContacts() {
int revision = adapter.getRevision();
listener.runOnDbThread(() -> {
try {
long start = now();
List<ContactListItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts()) {
try {
ContactId id = c.getId();
GroupCount count =
conversationManager.getGroupCount(id);
boolean connected =
connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count));
} catch (NoSuchContactException e) {
// Continue
}
}
logDuration(LOG, "Full load", start);
displayContacts(revision, contacts);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayContacts(int revision, List<ContactListItem> contacts) {
runOnUiThreadUnlessDestroyed(() -> {
if (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (contacts.isEmpty()) list.showData();
else adapter.replaceAll(contacts);
} else {
LOG.info("Concurrent update, reloading");
loadContacts();
}
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading");
loadContacts();
} else if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId(), true);
} else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId(), false);
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof ConversationMessageReceivedEvent) {
LOG.info("Conversation message received, updating item");
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h);
} else if (e instanceof PendingContactAddedEvent ||
e instanceof PendingContactRemovedEvent) {
checkForPendingContacts();
}
}
@UiThread
private void updateItem(ContactId c, ConversationMessageHeader h) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) {
item.addMessage(h);
adapter.updateItemAt(position, item);
}
}
@UiThread
private void removeItem(ContactId c) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) adapter.remove(item);
}
@UiThread
private void setConnected(ContactId c, boolean connected) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) {
item.setConnected(connected);
adapter.updateItemAt(position, item);
}
}
@UiThread @UiThread
private void showSnackBar() { private void showSnackBar() {
if (snackbar != null) return; if (snackbar != null) return;
@@ -335,5 +182,4 @@ public class ContactListFragment extends BaseFragment implements EventListener,
Intent i = new Intent(getContext(), PendingContactListActivity.class); Intent i = new Intent(getContext(), PendingContactListActivity.class);
startActivity(i); startActivity(i);
} }
} }

View File

@@ -2,31 +2,58 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.Immutable;
@NotThreadSafe @Immutable
@NotNullByDefault @NotNullByDefault
public class ContactListItem extends ContactItem { public class ContactListItem extends ContactItem
implements Comparable<ContactListItem> {
private boolean empty; private final boolean empty;
private long timestamp; private final long timestamp;
private int unread; private final int unread;
public ContactListItem(Contact contact, boolean connected, public ContactListItem(Contact contact, AuthorInfo authorInfo,
GroupCount count) { boolean connected, GroupCount count) {
super(contact, connected); super(contact, authorInfo, connected);
this.empty = count.getMsgCount() == 0; this.empty = count.getMsgCount() == 0;
this.unread = count.getUnreadCount(); this.unread = count.getUnreadCount();
this.timestamp = count.getLatestMsgTime(); this.timestamp = count.getLatestMsgTime();
} }
void addMessage(ConversationMessageHeader h) { private ContactListItem(Contact contact, AuthorInfo authorInfo,
empty = false; boolean connected, boolean empty, int unread, long timestamp) {
if (h.getTimestamp() > timestamp) timestamp = h.getTimestamp(); super(contact, authorInfo, connected);
if (!h.isRead()) unread++; this.empty = empty;
this.timestamp = timestamp;
this.unread = unread;
}
ContactListItem(ContactListItem item, boolean connected) {
this(item.getContact(), item.getAuthorInfo(), connected, item.empty,
item.unread, item.timestamp);
}
ContactListItem(ContactListItem item, ConversationMessageHeader h) {
this(item.getContact(), item.getAuthorInfo(), item.isConnected(), false,
h.isRead() ? item.unread : item.unread + 1,
Math.max(h.getTimestamp(), item.timestamp));
}
/**
* Creates a new copy of the given item with a new avatar
* referenced by the given attachment header.
*/
ContactListItem(ContactListItem item,
AttachmentHeader attachmentHeader) {
this(item.getContact(), new AuthorInfo(item.getAuthorInfo().getStatus(),
item.getAuthorInfo().getAlias(), attachmentHeader),
item.isConnected(), item.empty, item.unread, item.timestamp);
} }
boolean isEmpty() { boolean isEmpty() {
@@ -41,4 +68,8 @@ public class ContactListItem extends ContactItem {
return unread; return unread;
} }
@Override
public int compareTo(ContactListItem o) {
return Long.compare(o.getTimestamp(), timestamp);
}
} }

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