mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +01:00
Merge branch '1865-setupcontroller-to-viewmodel' into 'master'
Migrate SetupController to a ViewModel See merge request briar/briar!1340
This commit is contained in:
@@ -27,7 +27,9 @@ 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.contact.ContactListModule;
|
import org.briarproject.briar.android.contact.ContactListModule;
|
||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
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;
|
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,
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.briarproject.briar.android.account;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
interface DozeHelper {
|
||||||
|
|
||||||
|
boolean needToShowDozeFragment(Context context);
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ public class LiveEvent<T> extends LiveData<LiveEvent.ConsumableEvent<T>> {
|
|||||||
super.observe(owner, observer);
|
super.observe(owner, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void observeEventForever(LiveEventHandler<T> handler) {
|
||||||
|
LiveEventObserver<T> observer = new LiveEventObserver<>(handler);
|
||||||
|
super.observeForever(observer);
|
||||||
|
}
|
||||||
|
|
||||||
static class ConsumableEvent<T> {
|
static class ConsumableEvent<T> {
|
||||||
|
|
||||||
private final T content;
|
private final T content;
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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> T getOrAwaitValue(final LiveEvent<T> liveEvent)
|
||||||
|
throws InterruptedException {
|
||||||
|
final AtomicReference<T> data = new AtomicReference<>();
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
liveEvent.observeEventForever(new LiveEvent.LiveEventHandler<T>() {
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user