diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 747f60897..098243084 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -27,7 +27,9 @@ import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.StringUtils; +import org.briarproject.briar.android.account.DozeHelperModule; import org.briarproject.briar.android.account.LockManagerImpl; +import org.briarproject.briar.android.account.SetupModule; import org.briarproject.briar.android.contact.ContactListModule; import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.keyagreement.ContactExchangeModule; @@ -64,6 +66,8 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBL import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; @Module(includes = { + SetupModule.class, + DozeHelperModule.class, ContactExchangeModule.class, LoginModule.class, NavDrawerModule.class, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/AuthorNameFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/AuthorNameFragment.java index fee24208d..c41b0bbfb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/AuthorNameFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/AuthorNameFragment.java @@ -81,8 +81,7 @@ public class AuthorNameFragment extends SetupFragment { public void onClick(View view) { Editable text = authorNameInput.getText(); if (text != null) { - setupController.setAuthorName(text.toString().trim()); - setupController.showPasswordFragment(); + viewModel.setAuthorName(text.toString().trim()); } } 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 611cc1e18..e51c9497d 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 @@ -104,9 +104,15 @@ public class DozeFragment extends SetupFragment @Override public void onClick(View view) { - next.setVisibility(INVISIBLE); - progressBar.setVisibility(VISIBLE); - setupController.createAccount(); + setNextClicked(); + viewModel.dozeExceptionConfirmed(); } + @Override + void setNextClicked() { + super.setNextClicked(); + + next.setVisibility(INVISIBLE); + progressBar.setVisibility(VISIBLE); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelper.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelper.java new file mode 100644 index 000000000..f56431488 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelper.java @@ -0,0 +1,8 @@ +package org.briarproject.briar.android.account; + +import android.content.Context; + +interface DozeHelper { + + boolean needToShowDozeFragment(Context context); +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelperImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelperImpl.java new file mode 100644 index 000000000..a47f3ce92 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelperImpl.java @@ -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()); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelperModule.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelperModule.java new file mode 100644 index 000000000..fbbff364f --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeHelperModule.java @@ -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(); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java index 19958ad3f..d7cb2b385 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java @@ -30,7 +30,6 @@ import static org.briarproject.briar.android.util.UiUtils.setError; @MethodsNotNullByDefault @ParametersNotNullByDefault public class SetPasswordFragment extends SetupFragment { - private final static String TAG = SetPasswordFragment.class.getName(); private TextInputLayout passwordEntryWrapper; @@ -56,7 +55,7 @@ public class SetPasswordFragment extends SetupFragment { @Nullable Bundle savedInstanceState) { requireActivity().setTitle(getString(R.string.setup_password_intro)); View v = inflater.inflate(R.layout.fragment_setup_password, container, - false); + false); strengthMeter = v.findViewById(R.id.strength_meter); passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper); @@ -71,7 +70,7 @@ public class SetPasswordFragment extends SetupFragment { passwordConfirmation.addTextChangedListener(this); nextButton.setOnClickListener(this); - if (!setupController.needToShowDozeFragment()) { + if (!viewModel.needToShowDozeFragment()) { nextButton.setText(R.string.create_account_button); passwordConfirmation.setImeOptions(IME_ACTION_DONE); } @@ -97,7 +96,7 @@ public class SetPasswordFragment extends SetupFragment { strengthMeter .setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE); - float strength = setupController.estimatePasswordStrength(password1); + float strength = viewModel.estimatePasswordStrength(password1); strengthMeter.setStrength(strength); boolean strongEnough = strength >= QUITE_WEAK; @@ -117,14 +116,20 @@ public class SetPasswordFragment extends SetupFragment { IBinder token = passwordEntry.getWindowToken(); Object o = getContext().getSystemService(INPUT_METHOD_SERVICE); ((InputMethodManager) o).hideSoftInputFromWindow(token, 0); - setupController.setPassword(passwordEntry.getText().toString()); - if (setupController.needToShowDozeFragment()) { - setupController.showDozeFragment(); - } else { - nextButton.setVisibility(INVISIBLE); - progressBar.setVisibility(VISIBLE); - setupController.createAccount(); - } + + setNextClicked(); + viewModel.setPassword(passwordEntry.getText().toString()); } + @Override + void setNextClicked() { + super.setNextClicked(); + + passwordEntry.setFocusable(false); + passwordConfirmation.setFocusable(false); + if (!viewModel.needToShowDozeFragment()) { + nextButton.setVisibility(INVISIBLE); + progressBar.setVisibility(VISIBLE); + } + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java index 8d8a6ef4e..68d90ad95 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java @@ -4,7 +4,6 @@ import android.annotation.TargetApi; import android.content.Intent; import android.os.Bundle; -import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; @@ -15,28 +14,36 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener import javax.annotation.Nullable; import javax.inject.Inject; +import androidx.lifecycle.ViewModelProvider; + import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; +import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME; +import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED; +import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE; +import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED; +import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD; @MethodsNotNullByDefault @ParametersNotNullByDefault public class SetupActivity extends BaseActivity implements BaseFragmentListener { - private static final String STATE_KEY_AUTHOR_NAME = "authorName"; - private static final String STATE_KEY_PASSWORD = "password"; - @Inject - AccountManager accountManager; + ViewModelProvider.Factory viewModelFactory; + SetupViewModel viewModel; - @Inject - SetupController setupController; + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); - @Nullable - private String authorName, password; + viewModel = new ViewModelProvider(this, viewModelFactory) + .get(SetupViewModel.class); + viewModel.getState().observeEvent(this, this::onStateChanged); + } @Override public void onCreate(@Nullable Bundle state) { @@ -44,58 +51,27 @@ public class SetupActivity extends BaseActivity // fade-in after splash screen instead of default animation overridePendingTransition(R.anim.fade_in, R.anim.fade_out); setContentView(R.layout.activity_fragment_container); + } - if (state == null) { - if (accountManager.accountExists()) throw new AssertionError(); + private void onStateChanged(SetupViewModel.State state) { + if (state == AUTHOR_NAME) { showInitialFragment(AuthorNameFragment.newInstance()); - } else { - authorName = state.getString(STATE_KEY_AUTHOR_NAME); - password = state.getString(STATE_KEY_PASSWORD); + } else if (state == SET_PASSWORD) { + showPasswordFragment(); + } else if (state == DOZE) { + showDozeFragment(); + } else if (state == CREATED || state == FAILED) { + // TODO: Show an error if failed + showApp(); } } - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - setupController.setSetupActivity(this); - } - - @Override - protected void onSaveInstanceState(Bundle state) { - super.onSaveInstanceState(state); - if (authorName != null) - state.putString(STATE_KEY_AUTHOR_NAME, authorName); - if (password != null) - state.putString(STATE_KEY_PASSWORD, password); - } - - @Nullable - String getAuthorName() { - return authorName; - } - - void setAuthorName(String authorName) { - this.authorName = authorName; - } - - @Nullable - String getPassword() { - return password; - } - - void setPassword(String password) { - this.password = password; - } - void showPasswordFragment() { - if (authorName == null) throw new IllegalStateException(); showNextFragment(SetPasswordFragment.newInstance()); } @TargetApi(23) void showDozeFragment() { - if (authorName == null) throw new IllegalStateException(); - if (password == null) throw new IllegalStateException(); showNextFragment(DozeFragment.newInstance()); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupController.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupController.java deleted file mode 100644 index 2a701f151..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupController.java +++ /dev/null @@ -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(); -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupControllerImpl.java deleted file mode 100644 index 0b814420d..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupControllerImpl.java +++ /dev/null @@ -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 resultHandler = - new UiResultHandler(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 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)); - }); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java index bf7215fe6..dac85947b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java @@ -1,11 +1,13 @@ package org.briarproject.briar.android.account; +import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; @@ -17,7 +19,10 @@ import org.briarproject.briar.android.fragment.BaseFragment; import javax.inject.Inject; +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT; @@ -29,8 +34,40 @@ import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; abstract class SetupFragment extends BaseFragment implements TextWatcher, OnEditorActionListener, OnClickListener { + private final static String STATE_KEY_CLICKED = "setupFragmentClicked"; + private boolean clicked = false; + @Inject - SetupController setupController; + ViewModelProvider.Factory viewModelFactory; + SetupViewModel viewModel; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(requireActivity()) + .get(SetupViewModel.class); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + if (savedInstanceState != null) { + clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED); + } + if (clicked) { + setNextClicked(); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(STATE_KEY_CLICKED, clicked); + } + + @CallSuper + void setNextClicked() { + this.clicked = true; + } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupModule.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupModule.java new file mode 100644 index 000000000..1511f0d33 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupModule.java @@ -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); +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupViewModel.java new file mode 100644 index 000000000..d659e9475 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupViewModel.java @@ -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 = 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 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); + } + }); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java index 5d559ec47..08b0ec073 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java @@ -2,8 +2,6 @@ package org.briarproject.briar.android.activity; import android.app.Activity; -import org.briarproject.briar.android.account.SetupController; -import org.briarproject.briar.android.account.SetupControllerImpl; import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.DbController; @@ -35,13 +33,6 @@ public class ActivityModule { return activity; } - @ActivityScope - @Provides - SetupController provideSetupController( - SetupControllerImpl setupController) { - return setupController; - } - @ActivityScope @Provides protected BriarController provideBriarController( diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java index d4e1b9cff..15eb83e8e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java @@ -34,6 +34,11 @@ public class LiveEvent extends LiveData> { super.observe(owner, observer); } + public void observeEventForever(LiveEventHandler handler) { + LiveEventObserver observer = new LiveEventObserver<>(handler); + super.observeForever(observer); + } + static class ConsumableEvent { private final T content; diff --git a/briar-android/src/test/java/org/briarproject/briar/android/account/SetupControllerImplTest.java b/briar-android/src/test/java/org/briarproject/briar/android/account/SetupControllerImplTest.java deleted file mode 100644 index 057a097d9..000000000 --- a/briar-android/src/test/java/org/briarproject/briar/android/account/SetupControllerImplTest.java +++ /dev/null @@ -1,63 +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.test.BrambleMockTestCase; -import org.briarproject.bramble.test.ImmediateExecutor; -import org.jmock.Expectations; -import org.jmock.lib.legacy.ClassImposteriser; -import org.junit.Test; - -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; - -import static junit.framework.Assert.assertTrue; -import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; -import static org.briarproject.bramble.util.StringUtils.getRandomString; - -public class SetupControllerImplTest extends BrambleMockTestCase { - - private final AccountManager accountManager = - context.mock(AccountManager.class); - private final PasswordStrengthEstimator estimator = - context.mock(PasswordStrengthEstimator.class); - private final SetupActivity setupActivity; - - private final Executor ioExecutor = new ImmediateExecutor(); - - private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH); - private final String password = getRandomString(10); - - public SetupControllerImplTest() { - context.setImposteriser(ClassImposteriser.INSTANCE); - setupActivity = context.mock(SetupActivity.class); - } - - @Test - @SuppressWarnings("ResultOfMethodCallIgnored") - public void testCreateAccount() { - context.checking(new Expectations() {{ - // Set the author name and password - oneOf(setupActivity).setAuthorName(authorName); - oneOf(setupActivity).setPassword(password); - // Get the author name and password - oneOf(setupActivity).getAuthorName(); - will(returnValue(authorName)); - oneOf(setupActivity).getPassword(); - will(returnValue(password)); - // Create the account - oneOf(accountManager).createAccount(authorName, password); - will(returnValue(true)); - }}); - - SetupControllerImpl s = new SetupControllerImpl(accountManager, - ioExecutor, estimator); - s.setSetupActivity(setupActivity); - - AtomicBoolean called = new AtomicBoolean(false); - s.setAuthorName(authorName); - s.setPassword(password); - s.createAccount(result -> called.set(true)); - assertTrue(called.get()); - } -} diff --git a/briar-android/src/test/java/org/briarproject/briar/android/account/SetupViewModelTest.java b/briar-android/src/test/java/org/briarproject/briar/android/account/SetupViewModelTest.java new file mode 100644 index 000000000..e64693789 --- /dev/null +++ b/briar-android/src/test/java/org/briarproject/briar/android/account/SetupViewModelTest.java @@ -0,0 +1,73 @@ +package org.briarproject.briar.android.account; + +import android.app.Application; +import android.content.Context; + +import org.briarproject.bramble.api.account.AccountManager; +import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.bramble.test.ImmediateExecutor; +import org.briarproject.briar.android.account.SetupViewModel.State; +import org.jmock.Expectations; +import org.jmock.lib.legacy.ClassImposteriser; +import org.junit.Rule; +import org.junit.Test; + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule; + +import static junit.framework.Assert.assertEquals; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED; +import static org.briarproject.briar.android.viewmodel.LiveEventTestUtil.getOrAwaitValue; + +public class SetupViewModelTest extends BrambleMockTestCase { + + @Rule + public final InstantTaskExecutorRule testRule = + new InstantTaskExecutorRule(); + + private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH); + private final String password = getRandomString(10); + + private final Application app; + private final Context appContext; + private final AccountManager accountManager; + private final DozeHelper dozeHelper; + + public SetupViewModelTest() { + context.setImposteriser(ClassImposteriser.INSTANCE); + app = context.mock(Application.class); + appContext = context.mock(Context.class); + accountManager = context.mock(AccountManager.class); + dozeHelper = context.mock(DozeHelper.class); + } + + @Test + public void testCreateAccount() throws Exception { + context.checking(new Expectations() {{ + oneOf(accountManager).accountExists(); + will(returnValue(false)); + allowing(dozeHelper).needToShowDozeFragment(app); + allowing(app).getApplicationContext(); + will(returnValue(appContext)); + allowing(appContext).getPackageManager(); + + // Create the account + oneOf(accountManager).createAccount(authorName, password); + will(returnValue(true)); + }}); + + SetupViewModel viewModel = new SetupViewModel(app, + accountManager, + new ImmediateExecutor(), + context.mock(PasswordStrengthEstimator.class), + dozeHelper); + + viewModel.setAuthorName(authorName); + viewModel.setPassword(password); + + State state = getOrAwaitValue(viewModel.getState()); + assertEquals(CREATED, state); + } +} diff --git a/briar-android/src/test/java/org/briarproject/briar/android/viewmodel/LiveEventTestUtil.java b/briar-android/src/test/java/org/briarproject/briar/android/viewmodel/LiveEventTestUtil.java new file mode 100644 index 000000000..d0b83ebb4 --- /dev/null +++ b/briar-android/src/test/java/org/briarproject/briar/android/viewmodel/LiveEventTestUtil.java @@ -0,0 +1,34 @@ +/* Copyright 2019 Google LLC. + SPDX-License-Identifier: Apache-2.0 + https://gist.github.com/JoseAlcerreca/1e9ee05dcdd6a6a6fa1cbfc125559bba + */ + +package org.briarproject.briar.android.viewmodel; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class LiveEventTestUtil { + public static T getOrAwaitValue(final LiveEvent liveEvent) + throws InterruptedException { + final AtomicReference data = new AtomicReference<>(); + final CountDownLatch latch = new CountDownLatch(1); + liveEvent.observeEventForever(new LiveEvent.LiveEventHandler() { + @Override + public void onEvent(T o) { + data.set(o); + latch.countDown(); + // LiveEventHandler is wrapped internally in an observer that we + // don't have a reference to. Skip trying to remove the observer + // for now; all is torn down at the end of testing anyway. + } + }); + + // Don't wait indefinitely if the LiveEvent is not set. + if (!latch.await(2, TimeUnit.SECONDS)) { + throw new RuntimeException("LiveEvent value was never set."); + } + return data.get(); + } +}