diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxSettingsManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxSettingsManager.java index 3bd50d6c6..e2c8580bd 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxSettingsManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxSettingsManager.java @@ -35,6 +35,9 @@ public interface MailboxSettingsManager { void recordSuccessfulConnection(Transaction txn, long now) throws DbException; + void recordSuccessfulConnection(Transaction txn, long now, + List versions) throws DbException; + void recordFailedConnectionAttempt(Transaction txn, long now) throws DbException; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxManagerImpl.java index da8d8d47d..753e7cfef 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxManagerImpl.java @@ -9,10 +9,12 @@ import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxStatus; +import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.system.Clock; import java.io.IOException; +import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -98,34 +100,35 @@ class MailboxManagerImpl implements MailboxManager { @Override public boolean checkConnection() { - boolean success; + List versions = null; try { MailboxProperties props = db.transactionWithNullableResult(true, mailboxSettingsManager::getOwnMailboxProperties); if (props == null) throw new DbException(); - success = api.checkStatus(props); + versions = api.getServerSupports(props); } catch (DbException e) { logException(LOG, WARNING, e); // we don't treat this is a failure to record return false; } catch (IOException | MailboxApi.ApiException e) { // we record this as a failure - success = false; logException(LOG, WARNING, e); } try { - recordCheckResult(success); + recordCheckResult(versions); } catch (DbException e) { logException(LOG, WARNING, e); } - return success; + return versions != null; } - private void recordCheckResult(boolean success) throws DbException { + private void recordCheckResult(@Nullable List versions) + throws DbException { long now = clock.currentTimeMillis(); db.transaction(false, txn -> { - if (success) { - mailboxSettingsManager.recordSuccessfulConnection(txn, now); + if (versions != null) { + mailboxSettingsManager + .recordSuccessfulConnection(txn, now, versions); } else { mailboxSettingsManager.recordFailedConnectionAttempt(txn, now); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java index 23f9ca486..2a0aed3c6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImpl.java @@ -77,13 +77,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager { s.put(SETTINGS_KEY_ONION, p.getBaseUrl()); s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString()); List serverSupports = p.getServerSupports(); - int[] ints = new int[serverSupports.size() * 2]; - int i = 0; - for (MailboxVersion v : serverSupports) { - ints[i++] = v.getMajor(); - ints[i++] = v.getMinor(); - } - s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints); + encodeServerSupports(serverSupports, s); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); for (MailboxHook hook : hooks) { hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports()); @@ -121,14 +115,30 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager { @Override public void recordSuccessfulConnection(Transaction txn, long now) throws DbException { - Settings oldSettings = - settingsManager.getSettings(txn, SETTINGS_NAMESPACE); + recordSuccessfulConnection(txn, now, null); + } + + @Override + public void recordSuccessfulConnection(Transaction txn, long now, + @Nullable List versions) throws DbException { Settings s = new Settings(); + // fetch version that the server supports first + List serverSupports; + if (versions == null) { + Settings oldSettings = + settingsManager.getSettings(txn, SETTINGS_NAMESPACE); + serverSupports = parseServerSupports(oldSettings); + } else { + serverSupports = versions; + // store new versions + encodeServerSupports(serverSupports, s); + } + // now record the successful connection s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now); s.putLong(SETTINGS_KEY_LAST_SUCCESS, now); s.putInt(SETTINGS_KEY_ATTEMPTS, 0); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); - List serverSupports = parseServerSupports(oldSettings); + // broadcast status event MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports); txn.attach(new OwnMailboxConnectionStatusEvent(status)); } @@ -171,6 +181,17 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager { return filename; } + private void encodeServerSupports(List serverSupports, + Settings s) { + int[] ints = new int[serverSupports.size() * 2]; + int i = 0; + for (MailboxVersion v : serverSupports) { + ints[i++] = v.getMajor(); + ints[i++] = v.getMinor(); + } + s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints); + } + private List parseServerSupports(Settings s) throws DbException { if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxManagerImplTest.java new file mode 100644 index 000000000..ae1ee4666 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxManagerImplTest.java @@ -0,0 +1,128 @@ +package org.briarproject.bramble.mailbox; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.db.TransactionManager; +import org.briarproject.bramble.api.mailbox.MailboxProperties; +import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; +import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.bramble.test.DbExpectations; +import org.junit.Test; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.Executor; + +import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS; +import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; +import static org.briarproject.bramble.test.TestUtils.hasEvent; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class MailboxManagerImplTest extends BrambleMockTestCase { + + private final Executor ioExecutor = context.mock(Executor.class); + private final MailboxApi api = context.mock(MailboxApi.class); + private final TransactionManager db = + context.mock(TransactionManager.class); + private final MailboxSettingsManager mailboxSettingsManager = + context.mock(MailboxSettingsManager.class); + private final MailboxPairingTaskFactory pairingTaskFactory = + context.mock(MailboxPairingTaskFactory.class); + private final Clock clock = + context.mock(Clock.class); + + private final MailboxManagerImpl manager = new MailboxManagerImpl( + ioExecutor, api, db, mailboxSettingsManager, pairingTaskFactory, + clock); + + @Test + public void testDbExceptionDoesNotRecordFailure() throws Exception { + Transaction txn = new Transaction(null, true); + + context.checking(new DbExpectations() {{ + oneOf(db).transactionWithNullableResult(with(true), + withNullableDbCallable(txn)); + oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); + will(throwException(new DbException())); + }}); + + assertFalse(manager.checkConnection()); + assertFalse(hasEvent(txn, OwnMailboxConnectionStatusEvent.class)); + } + + @Test + public void testIOExceptionDoesRecordFailure() throws Exception { + Transaction txn = new Transaction(null, true); + Transaction txn2 = new Transaction(null, false); + MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS); + long now = new Random().nextLong(); + + context.checking(new DbExpectations() {{ + oneOf(db).transactionWithNullableResult(with(true), + withNullableDbCallable(txn)); + oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); + will(returnValue(props)); + oneOf(api).getServerSupports(props); + will(throwException(new IOException())); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(db).transaction(with(false), withDbRunnable(txn2)); + oneOf(mailboxSettingsManager) + .recordFailedConnectionAttempt(txn2, now); + }}); + + assertFalse(manager.checkConnection()); + } + + @Test + public void testApiExceptionDoesRecordFailure() throws Exception { + Transaction txn = new Transaction(null, true); + Transaction txn2 = new Transaction(null, false); + MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS); + long now = new Random().nextLong(); + + context.checking(new DbExpectations() {{ + oneOf(db).transactionWithNullableResult(with(true), + withNullableDbCallable(txn)); + oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); + will(returnValue(props)); + oneOf(api).getServerSupports(props); + will(throwException(new MailboxApi.ApiException())); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(db).transaction(with(false), withDbRunnable(txn2)); + oneOf(mailboxSettingsManager) + .recordFailedConnectionAttempt(txn2, now); + }}); + + assertFalse(manager.checkConnection()); + } + + @Test + public void testConnectionSuccess() throws Exception { + Transaction txn = new Transaction(null, true); + Transaction txn2 = new Transaction(null, false); + MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS); + long now = new Random().nextLong(); + + context.checking(new DbExpectations() {{ + oneOf(db).transactionWithNullableResult(with(true), + withNullableDbCallable(txn)); + oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); + will(returnValue(props)); + oneOf(api).getServerSupports(props); + will(returnValue(CLIENT_SUPPORTS)); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(db).transaction(with(false), withDbRunnable(txn2)); + oneOf(mailboxSettingsManager) + .recordSuccessfulConnection(txn2, now, CLIENT_SUPPORTS); + }}); + + assertTrue(manager.checkConnection()); + } + +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImplTest.java index 17a9330b6..0cdf08cdb 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxSettingsManagerImplTest.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Random; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS; @@ -165,6 +166,28 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase { hasEvent(txn, OwnMailboxConnectionStatusEvent.class); } + @Test + public void testRecordsSuccessWithVersions() throws Exception { + Transaction txn = new Transaction(null, false); + List versions = singletonList(new MailboxVersion(2, 1)); + Settings expectedSettings = new Settings(); + expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now); + expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now); + expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0); + expectedSettings.putInt(SETTINGS_KEY_SERVER_SUPPORTS, 0); + int[] newVersionsInts = {2, 1}; + expectedSettings + .putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, newVersionsInts); + + context.checking(new Expectations() {{ + oneOf(settingsManager).mergeSettings(txn, expectedSettings, + SETTINGS_NAMESPACE); + }}); + + manager.recordSuccessfulConnection(txn, now, versions); + hasEvent(txn, OwnMailboxConnectionStatusEvent.class); + } + @Test public void testRecordsFailureOnFirstAttempt() throws Exception { testRecordsFailure(new Settings(), 0); diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 2d020aeae..bc40fdc7a 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -100,7 +100,7 @@ dependencies { implementation project(path: ':briar-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default') implementation project(':bramble-android') - implementation 'org.briarproject:dont-kill-me-lib:0.2.2' + implementation 'org.briarproject:dont-kill-me-lib:0.2.5' implementation 'androidx.fragment:fragment:1.3.4' implementation 'androidx.preference:preference:1.1.1' diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 822717f27..007b82ee4 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -493,9 +493,6 @@ - - - diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java index 86ec70bfb..cec359705 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java @@ -32,7 +32,8 @@ public class DozeFragment extends SetupFragment private DozeView dozeView; private HuaweiProtectedAppsView huaweiProtectedAppsView; private HuaweiAppLaunchView huaweiAppLaunchView; - private XiaomiView xiaomiView; + private XiaomiRecentAppsView xiaomiRecentAppsView; + private XiaomiLockAppsView xiaomiLockAppsView; private Button next; private boolean secondAttempt = false; @@ -54,8 +55,10 @@ public class DozeFragment extends SetupFragment huaweiProtectedAppsView.setOnCheckedChangedListener(this); huaweiAppLaunchView = v.findViewById(R.id.huaweiAppLaunchView); huaweiAppLaunchView.setOnCheckedChangedListener(this); - xiaomiView = v.findViewById(R.id.xiaomiView); - xiaomiView.setOnCheckedChangedListener(this); + xiaomiRecentAppsView = v.findViewById(R.id.xiaomiRecentAppsView); + xiaomiRecentAppsView.setOnCheckedChangedListener(this); + xiaomiLockAppsView = v.findViewById(R.id.xiaomiLockAppsView); + xiaomiLockAppsView.setOnCheckedChangedListener(this); next = v.findViewById(R.id.next); ProgressBar progressBar = v.findViewById(R.id.progress); @@ -102,7 +105,8 @@ public class DozeFragment extends SetupFragment next.setEnabled(dozeView.isChecked() && huaweiProtectedAppsView.isChecked() && huaweiAppLaunchView.isChecked() && - xiaomiView.isChecked()); + xiaomiRecentAppsView.isChecked() && + xiaomiLockAppsView.isChecked()); } @SuppressLint("BatteryLife") diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeView.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeView.java index e4978bfd8..0d59345c7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeView.java @@ -50,7 +50,7 @@ class DozeView extends PowerView { onButtonClickListener.run(); } - public void setOnButtonClickListener(Runnable runnable) { + void setOnButtonClickListener(Runnable runnable) { onButtonClickListener = runnable; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiLockAppsView.java b/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiLockAppsView.java new file mode 100644 index 000000000..84cfd1cd9 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiLockAppsView.java @@ -0,0 +1,72 @@ +package org.briarproject.briar.android.account; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Toast; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +import androidx.annotation.StringRes; +import androidx.annotation.UiThread; + +import static android.widget.Toast.LENGTH_LONG; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.android.dontkillmelib.XiaomiUtils.getXiaomiLockAppsIntent; +import static org.briarproject.android.dontkillmelib.XiaomiUtils.xiaomiLockAppsNeedsToBeShown; +import static org.briarproject.bramble.util.LogUtils.logException; + +@UiThread +@NotNullByDefault +class XiaomiLockAppsView extends PowerView { + + private static final Logger LOG = + getLogger(XiaomiLockAppsView.class.getName()); + + public XiaomiLockAppsView(Context context) { + this(context, null); + } + + public XiaomiLockAppsView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public XiaomiLockAppsView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + setText(R.string.dnkm_xiaomi_lock_apps_text); + setButtonText(R.string.dnkm_xiaomi_lock_apps_button); + } + + @Override + public boolean needsToBeShown() { + return xiaomiLockAppsNeedsToBeShown(getContext()); + } + + @Override + @StringRes + protected int getHelpText() { + return R.string.dnkm_xiaomi_lock_apps_help; + } + + @Override + protected void onButtonClick() { + try { + getContext().startActivity(getXiaomiLockAppsIntent()); + setChecked(true); + return; + } catch (SecurityException e) { + logException(LOG, WARNING, e); + } + Toast.makeText(getContext(), + R.string.dnkm_xiaomi_lock_apps_error_toast, + LENGTH_LONG).show(); + // Let the user continue with setup + setChecked(true); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiView.java b/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiRecentAppsView.java similarity index 74% rename from briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiView.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiRecentAppsView.java index 453896b73..0efef138f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiRecentAppsView.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.account; - import android.content.Context; import android.util.AttributeSet; @@ -12,23 +11,23 @@ import javax.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.UiThread; -import static org.briarproject.android.dontkillmelib.XiaomiUtils.isMiuiTenOrLater; -import static org.briarproject.android.dontkillmelib.XiaomiUtils.isXiaomiOrRedmiDevice; +import static org.briarproject.android.dontkillmelib.XiaomiUtils.isMiuiVersionAtLeast; +import static org.briarproject.android.dontkillmelib.XiaomiUtils.xiaomiRecentAppsNeedsToBeShown; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; @UiThread @NotNullByDefault -class XiaomiView extends PowerView { +class XiaomiRecentAppsView extends PowerView { - public XiaomiView(Context context) { + public XiaomiRecentAppsView(Context context) { this(context, null); } - public XiaomiView(Context context, @Nullable AttributeSet attrs) { + public XiaomiRecentAppsView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - public XiaomiView(Context context, @Nullable AttributeSet attrs, + public XiaomiRecentAppsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setText(R.string.dnkm_xiaomi_text); @@ -37,7 +36,7 @@ class XiaomiView extends PowerView { @Override public boolean needsToBeShown() { - return isXiaomiOrRedmiDevice(); + return xiaomiRecentAppsNeedsToBeShown(); } @Override @@ -48,7 +47,7 @@ class XiaomiView extends PowerView { @Override protected void onButtonClick() { - int bodyRes = isMiuiTenOrLater() + int bodyRes = isMiuiVersionAtLeast(10, 0) ? R.string.dnkm_xiaomi_dialog_body_new : R.string.dnkm_xiaomi_dialog_body_old; showOnboardingDialog(getContext(), getContext().getString(bodyRes)); diff --git a/briar-android/src/main/res/layout/fragment_setup_doze.xml b/briar-android/src/main/res/layout/fragment_setup_doze.xml index e32cf10b0..5a94c3592 100644 --- a/briar-android/src/main/res/layout/fragment_setup_doze.xml +++ b/briar-android/src/main/res/layout/fragment_setup_doze.xml @@ -38,8 +38,8 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/huaweiProtectedAppsView" /> - + +