From 5ece6505daad349d6098d2014c11cf6bc4f5574a Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 29 Apr 2019 16:15:35 -0300 Subject: [PATCH] [android] Combine Password and OpenDatabase Activity into StartupActivity --- briar-android/src/main/AndroidManifest.xml | 13 +- .../briar/android/BriarService.java | 4 +- .../AuthorNameFragment.java | 2 +- .../{login => account}/DozeFragment.java | 4 +- .../android/{login => account}/DozeView.java | 2 +- .../{login => account}/HuaweiView.java | 2 +- .../android/{login => account}/PowerView.java | 2 +- .../android/account/SetPasswordFragment.java | 130 +++++++++++++ .../{login => account}/SetupActivity.java | 12 +- .../{login => account}/SetupController.java | 6 +- .../SetupControllerImpl.java | 18 +- .../{login => account}/SetupFragment.java | 2 +- .../{login => account}/UnlockActivity.java | 2 +- .../android/activity/ActivityComponent.java | 28 +-- .../android/activity/ActivityModule.java | 12 +- .../briar/android/activity/BriarActivity.java | 8 +- .../controller/BriarControllerImpl.java | 8 +- .../android/login/ChangePasswordActivity.java | 2 +- ...ler.java => ChangePasswordController.java} | 5 +- ...java => ChangePasswordControllerImpl.java} | 11 +- .../android/login/OpenDatabaseActivity.java | 101 ---------- .../android/login/OpenDatabaseFragment.java | 78 ++++++++ .../briar/android/login/PasswordActivity.java | 173 ------------------ .../briar/android/login/PasswordFragment.java | 169 +++++++++-------- .../briar/android/login/StartupActivity.java | 120 ++++++++++++ .../briar/android/login/StartupViewModel.java | 129 +++++++++++++ .../android/settings/SettingsFragment.java | 7 +- .../android/splash/SplashScreenActivity.java | 63 +++---- .../briar/android/test/TestDataActivity.java | 4 +- .../briar/android/util/UiUtils.java | 16 ++ .../android/viewmodel/ViewModelModule.java | 6 + .../src/main/res/layout/activity_unlock.xml | 2 +- ...atabase.xml => fragment_open_database.xml} | 0 ...ity_password.xml => fragment_password.xml} | 9 +- .../main/res/layout/fragment_setup_doze.xml | 4 +- .../SetPasswordFragmentTest.java} | 10 +- .../{login => account}/SetupActivityTest.java | 3 +- .../SetupControllerImplTest.java | 2 +- .../login/ChangePasswordActivityTest.java | 2 +- ... => ChangePasswordControllerImplTest.java} | 6 +- .../login/TestChangePasswordActivity.java | 3 +- 41 files changed, 696 insertions(+), 484 deletions(-) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/AuthorNameFragment.java (98%) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/DozeFragment.java (96%) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/DozeView.java (96%) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/HuaweiView.java (97%) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/PowerView.java (98%) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/SetupActivity.java (87%) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/SetupController.java (81%) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/SetupControllerImpl.java (86%) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/SetupFragment.java (97%) rename briar-android/src/main/java/org/briarproject/briar/android/{login => account}/UnlockActivity.java (99%) rename briar-android/src/main/java/org/briarproject/briar/android/login/{PasswordController.java => ChangePasswordController.java} (74%) rename briar-android/src/main/java/org/briarproject/briar/android/login/{PasswordControllerImpl.java => ChangePasswordControllerImpl.java} (79%) delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseActivity.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseFragment.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/StartupActivity.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/StartupViewModel.java rename briar-android/src/main/res/layout/{activity_open_database.xml => fragment_open_database.xml} (100%) rename briar-android/src/main/res/layout/{activity_password.xml => fragment_password.xml} (91%) rename briar-android/src/test/java/org/briarproject/briar/android/{login/PasswordFragmentTest.java => account/SetPasswordFragmentTest.java} (90%) rename briar-android/src/test/java/org/briarproject/briar/android/{login => account}/SetupActivityTest.java (93%) rename briar-android/src/test/java/org/briarproject/briar/android/{login => account}/SetupControllerImplTest.java (97%) rename briar-android/src/test/java/org/briarproject/briar/android/login/{PasswordControllerImplTest.java => ChangePasswordControllerImplTest.java} (87%) diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 51c5c7954..802d5cf8c 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -73,13 +73,13 @@ + android:windowSoftInputMode="stateUnchanged"> @@ -94,11 +94,6 @@ - - diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java index 606a6ba72..6d502b4b9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java @@ -22,7 +22,6 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.R; import org.briarproject.briar.android.logout.HideUiActivity; -import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.LockManager; @@ -48,6 +47,7 @@ import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; +import static org.briarproject.briar.android.activity.ActivityComponent.ENTRY_ACTIVITY; import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID; @@ -182,7 +182,7 @@ public class BriarService extends Service { NotificationManager nm = (NotificationManager) o; nm.notify(FAILURE_NOTIFICATION_ID, b.build()); // Bring the dashboard to the front to clear the back stack - i = new Intent(BriarService.this, NavDrawerActivity.class); + i = new Intent(BriarService.this, ENTRY_ACTIVITY); i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); i.putExtra(EXTRA_STARTUP_FAILED, true); startActivity(i); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/AuthorNameFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/AuthorNameFragment.java similarity index 98% rename from briar-android/src/main/java/org/briarproject/briar/android/login/AuthorNameFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/AuthorNameFragment.java index 109a9e153..8a9104d96 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/AuthorNameFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/AuthorNameFragment.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.os.Bundle; import android.support.design.widget.TextInputEditText; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java similarity index 96% rename from briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java index 2a180b3dc..7dd63ee4d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.annotation.SuppressLint; import android.content.Intent; @@ -14,7 +14,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.login.PowerView.OnCheckedChangedListener; +import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener; import org.briarproject.briar.android.util.UiUtils; import static android.view.View.INVISIBLE; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeView.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeView.java similarity index 96% rename from briar-android/src/main/java/org/briarproject/briar/android/login/DozeView.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/DozeView.java index a2b1c5186..e02f4f2e5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeView.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.content.Context; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/HuaweiView.java b/briar-android/src/main/java/org/briarproject/briar/android/account/HuaweiView.java similarity index 97% rename from briar-android/src/main/java/org/briarproject/briar/android/login/HuaweiView.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/HuaweiView.java index c566ce697..2396aab25 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/HuaweiView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/HuaweiView.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.content.Context; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PowerView.java b/briar-android/src/main/java/org/briarproject/briar/android/account/PowerView.java similarity index 98% rename from briar-android/src/main/java/org/briarproject/briar/android/login/PowerView.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/PowerView.java index ffcaf3409..032826ca5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PowerView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/PowerView.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.content.Context; import android.os.Parcel; 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 new file mode 100644 index 000000000..9895530d1 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java @@ -0,0 +1,130 @@ +package org.briarproject.briar.android.account; + +import android.os.Bundle; +import android.os.IBinder; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.ProgressBar; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.login.StrengthMeter; + +import javax.annotation.Nullable; + +import static android.content.Context.INPUT_METHOD_SERVICE; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; +import static java.util.Objects.requireNonNull; +import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; +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; + private TextInputLayout passwordConfirmationWrapper; + private TextInputEditText passwordEntry; + private TextInputEditText passwordConfirmation; + private StrengthMeter strengthMeter; + private Button nextButton; + private ProgressBar progressBar; + + public static SetPasswordFragment newInstance() { + return new SetPasswordFragment(); + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + requireNonNull(getActivity()).setTitle(getString(R.string.setup_password_intro)); + View v = inflater.inflate(R.layout.fragment_setup_password, container, + false); + + strengthMeter = v.findViewById(R.id.strength_meter); + passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper); + passwordEntry = v.findViewById(R.id.password_entry); + passwordConfirmationWrapper = + v.findViewById(R.id.password_confirm_wrapper); + passwordConfirmation = v.findViewById(R.id.password_confirm); + nextButton = v.findViewById(R.id.next); + progressBar = v.findViewById(R.id.progress); + + passwordEntry.addTextChangedListener(this); + passwordConfirmation.addTextChangedListener(this); + nextButton.setOnClickListener(this); + + if (!setupController.needToShowDozeFragment()) { + nextButton.setText(R.string.create_account_button); + passwordConfirmation.setImeOptions(IME_ACTION_DONE); + } + + return v; + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + protected String getHelpText() { + return getString(R.string.setup_password_explanation); + } + + @Override + public void onTextChanged(CharSequence authorName, int i, int i1, int i2) { + String password1 = passwordEntry.getText().toString(); + String password2 = passwordConfirmation.getText().toString(); + boolean passwordsMatch = password1.equals(password2); + + strengthMeter + .setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE); + float strength = setupController.estimatePasswordStrength(password1); + strengthMeter.setStrength(strength); + boolean strongEnough = strength >= QUITE_WEAK; + + setError(passwordEntryWrapper, getString(R.string.password_too_weak), + password1.length() > 0 && !strongEnough); + setError(passwordConfirmationWrapper, + getString(R.string.passwords_do_not_match), + password2.length() > 0 && !passwordsMatch); + + boolean enabled = passwordsMatch && strongEnough; + nextButton.setEnabled(enabled); + passwordConfirmation.setOnEditorActionListener(enabled ? this : null); + } + + @Override + public void onClick(View view) { + 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(); + } + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java similarity index 87% rename from briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java index e38cfeb0c..dfa340597 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.annotation.TargetApi; import android.content.Intent; @@ -15,8 +15,11 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener import javax.annotation.Nullable; import javax.inject.Inject; +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.activity.ActivityComponent.ENTRY_ACTIVITY; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -86,7 +89,7 @@ public class SetupActivity extends BaseActivity void showPasswordFragment() { if (authorName == null) throw new IllegalStateException(); - showNextFragment(PasswordFragment.newInstance()); + showNextFragment(SetPasswordFragment.newInstance()); } @TargetApi(23) @@ -97,8 +100,9 @@ public class SetupActivity extends BaseActivity } void showApp() { - Intent i = new Intent(this, OpenDatabaseActivity.class); - i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME); + Intent i = new Intent(this, ENTRY_ACTIVITY); + i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME | + FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); supportFinishAfterTransition(); overridePendingTransition(R.anim.screen_new_in, R.anim.screen_old_out); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupController.java similarity index 81% rename from briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/SetupController.java index 392151897..2a701f151 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupController.java @@ -1,9 +1,9 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @NotNullByDefault -public interface SetupController extends PasswordController { +public interface SetupController { void setSetupActivity(SetupActivity setupActivity); @@ -11,6 +11,8 @@ public interface SetupController extends PasswordController { void setAuthorName(String authorName); + float estimatePasswordStrength(String password); + void setPassword(String password); /** diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupControllerImpl.java similarity index 86% rename from briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/SetupControllerImpl.java index 02f40226e..89f88f622 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupControllerImpl.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.support.annotation.Nullable; @@ -15,12 +15,15 @@ import java.util.logging.Logger; import javax.inject.Inject; @NotNullByDefault -public class SetupControllerImpl extends PasswordControllerImpl - implements SetupController { +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; @@ -28,7 +31,9 @@ public class SetupControllerImpl extends PasswordControllerImpl SetupControllerImpl(AccountManager accountManager, @IoExecutor Executor ioExecutor, PasswordStrengthEstimator strengthEstimator) { - super(accountManager, ioExecutor, strengthEstimator); + this.accountManager = accountManager; + this.strengthEstimator = strengthEstimator; + this.ioExecutor = ioExecutor; } @Override @@ -51,6 +56,11 @@ public class SetupControllerImpl extends PasswordControllerImpl setupActivity.setAuthorName(authorName); } + @Override + public float estimatePasswordStrength(String password) { + return strengthEstimator.estimateStrength(password); + } + @Override public void setPassword(String password) { SetupActivity setupActivity = this.setupActivity; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java similarity index 97% rename from briar-android/src/main/java/org/briarproject/briar/android/login/SetupFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java index ba5da40de..acbcc8252 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.support.annotation.Nullable; import android.text.Editable; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/account/UnlockActivity.java similarity index 99% rename from briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java rename to briar-android/src/main/java/org/briarproject/briar/android/account/UnlockActivity.java index 2842ecb3e..cbbcf3d09 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/UnlockActivity.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.login; +package org.briarproject.briar.android.account; import android.app.KeyguardManager; import android.content.Intent; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index 1c0e681ef..f87df6442 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -4,6 +4,11 @@ import android.app.Activity; import org.briarproject.briar.android.AndroidComponent; import org.briarproject.briar.android.StartupFailureActivity; +import org.briarproject.briar.android.account.AuthorNameFragment; +import org.briarproject.briar.android.account.DozeFragment; +import org.briarproject.briar.android.account.SetPasswordFragment; +import org.briarproject.briar.android.account.SetupActivity; +import org.briarproject.briar.android.account.UnlockActivity; import org.briarproject.briar.android.blog.BlogActivity; import org.briarproject.briar.android.blog.BlogFragment; import org.briarproject.briar.android.blog.BlogModule; @@ -37,14 +42,10 @@ import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment; import org.briarproject.briar.android.keyagreement.KeyAgreementActivity; import org.briarproject.briar.android.keyagreement.KeyAgreementFragment; -import org.briarproject.briar.android.login.AuthorNameFragment; import org.briarproject.briar.android.login.ChangePasswordActivity; -import org.briarproject.briar.android.login.DozeFragment; -import org.briarproject.briar.android.login.OpenDatabaseActivity; -import org.briarproject.briar.android.login.PasswordActivity; +import org.briarproject.briar.android.login.OpenDatabaseFragment; import org.briarproject.briar.android.login.PasswordFragment; -import org.briarproject.briar.android.login.SetupActivity; -import org.briarproject.briar.android.login.UnlockActivity; +import org.briarproject.briar.android.login.StartupActivity; import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.panic.PanicPreferencesActivity; import org.briarproject.briar.android.panic.PanicResponderActivity; @@ -90,18 +91,18 @@ import dagger.Component; dependencies = AndroidComponent.class) public interface ActivityComponent { + Class ENTRY_ACTIVITY = NavDrawerActivity.class; + Activity activity(); void inject(SplashScreenActivity activity); + void inject(StartupActivity activity); + void inject(SetupActivity activity); - void inject(OpenDatabaseActivity activity); - void inject(NavDrawerActivity activity); - void inject(PasswordActivity activity); - void inject(PanicResponderActivity activity); void inject(PanicPreferencesActivity activity); @@ -177,12 +178,17 @@ public interface ActivityComponent { void inject(PendingContactListActivity activity); // Fragments + void inject(AuthorNameFragment fragment); - void inject(PasswordFragment fragment); + void inject(SetPasswordFragment fragment); void inject(DozeFragment fragment); + void inject(PasswordFragment imageFragment); + + void inject(OpenDatabaseFragment activity); + void inject(ContactListFragment fragment); void inject(CreateGroupFragment fragment); 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 b21d07232..52b8582f4 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 @@ -6,10 +6,10 @@ import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.DbController; import org.briarproject.briar.android.controller.DbControllerImpl; -import org.briarproject.briar.android.login.PasswordController; -import org.briarproject.briar.android.login.PasswordControllerImpl; -import org.briarproject.briar.android.login.SetupController; -import org.briarproject.briar.android.login.SetupControllerImpl; +import org.briarproject.briar.android.login.ChangePasswordController; +import org.briarproject.briar.android.login.ChangePasswordControllerImpl; +import org.briarproject.briar.android.account.SetupController; +import org.briarproject.briar.android.account.SetupControllerImpl; import org.briarproject.briar.android.navdrawer.NavDrawerController; import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl; @@ -48,8 +48,8 @@ public class ActivityModule { @ActivityScope @Provides - PasswordController providePasswordController( - PasswordControllerImpl passwordController) { + ChangePasswordController providePasswordController( + ChangePasswordControllerImpl passwordController) { return passwordController; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java index 917dce9b6..8f476d456 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java @@ -15,8 +15,8 @@ import org.briarproject.briar.R; import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.DbController; import org.briarproject.briar.android.controller.handler.UiResultHandler; -import org.briarproject.briar.android.login.PasswordActivity; -import org.briarproject.briar.android.login.UnlockActivity; +import org.briarproject.briar.android.login.StartupActivity; +import org.briarproject.briar.android.account.UnlockActivity; import org.briarproject.briar.android.logout.ExitActivity; import org.briarproject.briar.api.android.LockManager; @@ -86,8 +86,8 @@ public abstract class BriarActivity extends BaseActivity { if (!briarController.accountSignedIn() && !isFinishing()) { // Also check that the activity isn't finishing already. // This is possible if we finished in onActivityResult(). - // Launching another PasswordActivity would cause a loop. - Intent i = new Intent(this, PasswordActivity.class); + // Launching another StartupActivity would cause a loop. + Intent i = new Intent(this, StartupActivity.class); startActivityForResult(i, REQUEST_PASSWORD); } else if (lockManager.isLocked() && !isFinishing()) { // Also check that the activity isn't finishing already. diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java index d492f0472..7eb7a1bce 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java @@ -8,6 +8,7 @@ import android.support.annotation.CallSuper; import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.briar.android.BriarService; @@ -21,6 +22,7 @@ import java.util.logging.Logger; import javax.inject.Inject; import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; @@ -34,6 +36,7 @@ public class BriarControllerImpl implements BriarController { private final BriarServiceConnection serviceConnection; private final AccountManager accountManager; + private final LifecycleManager lifecycleManager; private final Executor databaseExecutor; private final SettingsManager settingsManager; private final DozeWatchdog dozeWatchdog; @@ -44,11 +47,13 @@ public class BriarControllerImpl implements BriarController { @Inject BriarControllerImpl(BriarServiceConnection serviceConnection, AccountManager accountManager, + LifecycleManager lifecycleManager, @DatabaseExecutor Executor databaseExecutor, SettingsManager settingsManager, DozeWatchdog dozeWatchdog, Activity activity) { this.serviceConnection = serviceConnection; this.accountManager = accountManager; + this.lifecycleManager = lifecycleManager; this.databaseExecutor = databaseExecutor; this.settingsManager = settingsManager; this.dozeWatchdog = dozeWatchdog; @@ -84,7 +89,8 @@ public class BriarControllerImpl implements BriarController { @Override public boolean accountSignedIn() { - return accountManager.hasDatabaseKey(); + return accountManager.hasDatabaseKey() && + lifecycleManager.getLifecycleState().isAfter(STARTING_SERVICES); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordActivity.java index 8a0604c1f..136e9e19d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordActivity.java @@ -31,7 +31,7 @@ public class ChangePasswordActivity extends BriarActivity implements OnClickListener, OnEditorActionListener { @Inject - protected PasswordController passwordController; + protected ChangePasswordController passwordController; private TextInputLayout currentPasswordEntryWrapper; private TextInputLayout newPasswordEntryWrapper; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordController.java b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordController.java similarity index 74% rename from briar-android/src/main/java/org/briarproject/briar/android/login/PasswordController.java rename to briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordController.java index cef864152..54e8ab55d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordController.java @@ -4,13 +4,10 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.android.controller.handler.ResultHandler; @NotNullByDefault -public interface PasswordController { +public interface ChangePasswordController { float estimatePasswordStrength(String password); - void validatePassword(String password, - ResultHandler resultHandler); - void changePassword(String oldPassword, String newPassword, ResultHandler resultHandler); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordControllerImpl.java similarity index 79% rename from briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java rename to briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordControllerImpl.java index 0ecf78475..79eaef917 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordControllerImpl.java @@ -11,14 +11,14 @@ import java.util.concurrent.Executor; import javax.inject.Inject; @NotNullByDefault -public class PasswordControllerImpl implements PasswordController { +public class ChangePasswordControllerImpl implements ChangePasswordController { protected final AccountManager accountManager; protected final Executor ioExecutor; private final PasswordStrengthEstimator strengthEstimator; @Inject - PasswordControllerImpl(AccountManager accountManager, + ChangePasswordControllerImpl(AccountManager accountManager, @IoExecutor Executor ioExecutor, PasswordStrengthEstimator strengthEstimator) { this.accountManager = accountManager; @@ -31,13 +31,6 @@ public class PasswordControllerImpl implements PasswordController { return strengthEstimator.estimateStrength(password); } - @Override - public void validatePassword(String password, - ResultHandler resultHandler) { - ioExecutor.execute(() -> - resultHandler.onResult(accountManager.signIn(password))); - } - @Override public void changePassword(String oldPassword, String newPassword, ResultHandler resultHandler) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseActivity.java deleted file mode 100644 index 2f207d79c..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseActivity.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.briarproject.briar.android.login; - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.widget.ImageView; -import android.widget.TextView; - -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.event.EventBus; -import org.briarproject.bramble.api.event.EventListener; -import org.briarproject.bramble.api.lifecycle.LifecycleManager; -import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState; -import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; -import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; -import org.briarproject.briar.R; -import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.activity.BriarActivity; -import org.briarproject.briar.android.navdrawer.NavDrawerActivity; - -import javax.inject.Inject; - -import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE; -import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE; -import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; - -@ParametersNotNullByDefault -public class OpenDatabaseActivity extends BriarActivity - implements EventListener { - - @Inject - LifecycleManager lifecycleManager; - @Inject - EventBus eventBus; - - private TextView textView; - private ImageView imageView; - private boolean showingMigration = false, showingCompaction = false; - - @Override - public void onCreate(@Nullable Bundle state) { - super.onCreate(state); - setContentView(R.layout.activity_open_database); - textView = findViewById(R.id.textView); - imageView = findViewById(R.id.imageView); - } - - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - - @Override - public void onStart() { - super.onStart(); - LifecycleState state = lifecycleManager.getLifecycleState(); - if (state.isAfter(STARTING_SERVICES)) { - finishAndStartApp(); - } else { - if (state == MIGRATING_DATABASE) showMigration(); - else if (state == COMPACTING_DATABASE) showCompaction(); - eventBus.addListener(this); - } - } - - @Override - protected void onStop() { - super.onStop(); - eventBus.removeListener(this); - } - - @Override - public void eventOccurred(Event e) { - if (e instanceof LifecycleEvent) { - LifecycleState state = ((LifecycleEvent) e).getLifecycleState(); - if (state.isAfter(STARTING_SERVICES)) finishAndStartApp(); - else if (state == MIGRATING_DATABASE) showMigration(); - else if (state == COMPACTING_DATABASE) showCompaction(); - } - } - - private void showMigration() { - if (showingMigration) return; - textView.setText(R.string.startup_migrate_database); - imageView.setImageResource(R.drawable.startup_migration); - showingMigration = true; - } - - private void showCompaction() { - if (showingCompaction) return; - textView.setText(R.string.startup_compact_database); - imageView.setImageResource(R.drawable.startup_migration); - showingCompaction = true; - } - - private void finishAndStartApp() { - startActivity(new Intent(this, NavDrawerActivity.class)); - supportFinishAfterTransition(); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseFragment.java new file mode 100644 index 000000000..1a33b77f8 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/OpenDatabaseFragment.java @@ -0,0 +1,78 @@ +package org.briarproject.briar.android.login; + +import android.arch.lifecycle.ViewModelProvider; +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.fragment.BaseFragment; + +import javax.inject.Inject; + +import static org.briarproject.briar.android.login.StartupViewModel.State; +import static org.briarproject.briar.android.login.StartupViewModel.State.COMPACTING; +import static org.briarproject.briar.android.login.StartupViewModel.State.MIGRATING; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class OpenDatabaseFragment extends BaseFragment { + + final static String TAG = PasswordFragment.class.getName(); + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private TextView textView; + private ImageView imageView; + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_open_database, container, + false); + textView = v.findViewById(R.id.textView); + imageView = v.findViewById(R.id.imageView); + + StartupViewModel viewModel = ViewModelProviders.of(requireActivity(), + viewModelFactory).get(StartupViewModel.class); + viewModel.getState().observe(this, this::onStateChanged); + + return v; + } + + private void onStateChanged(State state) { + if (state == MIGRATING) showMigration(); + else if (state == COMPACTING) showCompaction(); + } + + private void showMigration() { + textView.setText(R.string.startup_migrate_database); + imageView.setImageResource(R.drawable.startup_migration); + } + + private void showCompaction() { + textView.setText(R.string.startup_compact_database); + imageView.setImageResource(R.drawable.startup_migration); + } + + @Override + public String getUniqueTag() { + return TAG; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java deleted file mode 100644 index e8b20f243..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.briarproject.briar.android.login; - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.TextInputLayout; -import android.support.v7.app.AlertDialog; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; - -import org.briarproject.bramble.api.account.AccountManager; -import org.briarproject.briar.R; -import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.activity.BaseActivity; -import org.briarproject.briar.android.controller.BriarController; -import org.briarproject.briar.android.controller.handler.UiResultHandler; -import org.briarproject.briar.android.util.UiUtils; -import org.briarproject.briar.api.android.AndroidNotificationManager; - -import javax.inject.Inject; - -import static android.view.View.INVISIBLE; -import static android.view.View.VISIBLE; -import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; -import static org.briarproject.briar.android.util.UiUtils.enterPressed; - -public class PasswordActivity extends BaseActivity { - - @Inject - AccountManager accountManager; - - @Inject - AndroidNotificationManager notificationManager; - - @Inject - PasswordController passwordController; - - @Inject - BriarController briarController; - - private Button signInButton; - private ProgressBar progress; - private TextInputLayout input; - private EditText password; - - @Override - public void onCreate(Bundle state) { - super.onCreate(state); - // fade-in after splash screen instead of default animation - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - - if (!accountManager.accountExists()) { - setResult(RESULT_CANCELED); - finish(); - return; - } - - setContentView(R.layout.activity_password); - signInButton = findViewById(R.id.btn_sign_in); - progress = findViewById(R.id.progress_wheel); - input = findViewById(R.id.password_layout); - password = findViewById(R.id.edit_password); - password.setOnEditorActionListener((v, actionId, event) -> { - if (actionId == IME_ACTION_DONE || enterPressed(actionId, event)) { - validatePassword(); - return true; - } - return false; - }); - password.addTextChangedListener(new TextWatcher() { - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - if (count > 0) UiUtils.setError(input, null, false); - } - - @Override - public void afterTextChanged(Editable s) { - } - }); - } - - @Override - public void onStart() { - super.onStart(); - // If the user has already signed in, clean up this instance - if (briarController.accountSignedIn()) { - setResult(RESULT_OK); - finish(); - } else { - notificationManager.blockSignInNotification(); - notificationManager.clearSignInNotification(); - } - } - - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - - @Override - public void onBackPressed() { - // Move task and activity to the background instead of showing another - // password prompt. onActivityResult() won't be called in BriarActivity - moveTaskToBack(true); - } - - private void deleteAccount() { - accountManager.deleteAccount(); - startActivity(new Intent(this, SetupActivity.class)); - setResult(RESULT_CANCELED); - finish(); - } - - public void onSignInClick(View v) { - validatePassword(); - } - - public void onForgottenPasswordClick(View v) { - // TODO Encapsulate the dialog in a re-usable fragment - AlertDialog.Builder builder = new AlertDialog.Builder(this, - R.style.BriarDialogTheme); - builder.setTitle(R.string.dialog_title_lost_password); - builder.setMessage(R.string.dialog_message_lost_password); - builder.setPositiveButton(R.string.cancel, null); - builder.setNegativeButton(R.string.delete, - (dialog, which) -> deleteAccount()); - AlertDialog dialog = builder.create(); - dialog.show(); - } - - private void validatePassword() { - hideSoftKeyboard(password); - signInButton.setVisibility(INVISIBLE); - progress.setVisibility(VISIBLE); - passwordController.validatePassword(password.getText().toString(), - new UiResultHandler(this) { - @Override - public void onResultUi(@NonNull Boolean result) { - if (result) { - setResult(RESULT_OK); - supportFinishAfterTransition(); - // don't show closing animation, - // but one for opening NavDrawerActivity - overridePendingTransition(R.anim.screen_new_in, - R.anim.screen_old_out); - } else { - tryAgain(); - } - } - }); - } - - private void tryAgain() { - UiUtils.setError(input, getString(R.string.try_again), true); - signInButton.setVisibility(VISIBLE); - progress.setVisibility(INVISIBLE); - password.setText(""); - - // show the keyboard again - showSoftKeyboard(password); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java index 29950050f..d83421ad2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java @@ -1,48 +1,50 @@ package org.briarproject.briar.android.login; +import android.arch.lifecycle.ViewModelProvider; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; -import android.os.IBinder; -import android.support.design.widget.TextInputEditText; import android.support.design.widget.TextInputLayout; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; import android.widget.Button; +import android.widget.EditText; import android.widget.ProgressBar; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import org.briarproject.briar.android.util.UiUtils; +import org.briarproject.briar.android.fragment.BaseFragment; import javax.annotation.Nullable; +import javax.inject.Inject; -import static android.content.Context.INPUT_METHOD_SERVICE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; -import static java.util.Objects.requireNonNull; -import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; +import static org.briarproject.briar.android.util.UiUtils.enterPressed; +import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard; +import static org.briarproject.briar.android.util.UiUtils.setError; +import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard; @MethodsNotNullByDefault @ParametersNotNullByDefault -public class PasswordFragment extends SetupFragment { +public class PasswordFragment extends BaseFragment implements TextWatcher { private final static String TAG = PasswordFragment.class.getName(); - private TextInputLayout passwordEntryWrapper; - private TextInputLayout passwordConfirmationWrapper; - private TextInputEditText passwordEntry; - private TextInputEditText passwordConfirmation; - private StrengthMeter strengthMeter; - private Button nextButton; - private ProgressBar progressBar; + @Inject + ViewModelProvider.Factory viewModelFactory; - public static PasswordFragment newInstance() { - return new PasswordFragment(); - } + private StartupViewModel viewModel; + private Button signInButton; + private ProgressBar progress; + private TextInputLayout input; + private EditText password; @Override public void injectFragment(ActivityComponent component) { @@ -53,78 +55,85 @@ public class PasswordFragment extends SetupFragment { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - requireNonNull(getActivity()).setTitle(getString(R.string.setup_password_intro)); - View v = inflater.inflate(R.layout.fragment_setup_password, container, + View v = inflater.inflate(R.layout.fragment_password, container, false); - strengthMeter = v.findViewById(R.id.strength_meter); - passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper); - passwordEntry = v.findViewById(R.id.password_entry); - passwordConfirmationWrapper = - v.findViewById(R.id.password_confirm_wrapper); - passwordConfirmation = v.findViewById(R.id.password_confirm); - nextButton = v.findViewById(R.id.next); - progressBar = v.findViewById(R.id.progress); + viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory) + .get(StartupViewModel.class); + viewModel.getPasswordValidated().observe(this, this::onPasswordValidated); - passwordEntry.addTextChangedListener(this); - passwordConfirmation.addTextChangedListener(this); - nextButton.setOnClickListener(this); - - if (!setupController.needToShowDozeFragment()) { - nextButton.setText(R.string.create_account_button); - passwordConfirmation.setImeOptions(IME_ACTION_DONE); - } + signInButton = v.findViewById(R.id.btn_sign_in); + signInButton.setOnClickListener(view -> onSignInButtonClicked()); + progress = v.findViewById(R.id.progress_wheel); + input = v.findViewById(R.id.password_layout); + password = v.findViewById(R.id.edit_password); + password.setOnEditorActionListener((view, actionId, event) -> { + if (actionId == IME_ACTION_DONE || enterPressed(actionId, event)) { + onSignInButtonClicked(); + return true; + } + return false; + }); + password.addTextChangedListener(this); + v.findViewById(R.id.btn_forgotten) + .setOnClickListener(view -> onForgottenPasswordClick()); return v; } + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + if (count > 0) setError(input, null, false); + } + + @Override + public void afterTextChanged(Editable s) { + } + + private void onSignInButtonClicked() { + hideSoftKeyboard(password); + signInButton.setVisibility(INVISIBLE); + progress.setVisibility(VISIBLE); + viewModel.validatePassword(password.getText().toString()); + } + + private void onPasswordValidated(@Nullable Boolean valid) { + if (valid != null && !valid) { + setError(input, getString(R.string.try_again), true); + signInButton.setVisibility(VISIBLE); + progress.setVisibility(INVISIBLE); + password.setText(null); + + // show the keyboard again + showSoftKeyboard(password); + + // reset validation state for configuration changes + viewModel.getPasswordValidated().setValue(null); + } + } + + public void onForgottenPasswordClick() { + // TODO Encapsulate the dialog in a re-usable fragment + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(), + R.style.BriarDialogTheme); + builder.setTitle(R.string.dialog_title_lost_password); + builder.setMessage(R.string.dialog_message_lost_password); + builder.setPositiveButton(R.string.cancel, null); + builder.setNegativeButton(R.string.delete, + (dialog, which) -> viewModel.deleteAccount()); + AlertDialog dialog = builder.create(); + dialog.show(); + } + @Override public String getUniqueTag() { return TAG; } - @Override - protected String getHelpText() { - return getString(R.string.setup_password_explanation); - } - - @Override - public void onTextChanged(CharSequence authorName, int i, int i1, int i2) { - String password1 = passwordEntry.getText().toString(); - String password2 = passwordConfirmation.getText().toString(); - boolean passwordsMatch = password1.equals(password2); - - strengthMeter - .setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE); - float strength = setupController.estimatePasswordStrength(password1); - strengthMeter.setStrength(strength); - boolean strongEnough = strength >= QUITE_WEAK; - - UiUtils.setError(passwordEntryWrapper, - getString(R.string.password_too_weak), - password1.length() > 0 && !strongEnough); - UiUtils.setError(passwordConfirmationWrapper, - getString(R.string.passwords_do_not_match), - password2.length() > 0 && !passwordsMatch); - - boolean enabled = passwordsMatch && strongEnough; - nextButton.setEnabled(enabled); - passwordConfirmation.setOnEditorActionListener(enabled ? this : null); - } - - @Override - public void onClick(View view) { - 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(); - } - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/StartupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/StartupActivity.java new file mode 100644 index 000000000..6b29c74a7 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/StartupActivity.java @@ -0,0 +1,120 @@ +package org.briarproject.briar.android.login; + +import android.arch.lifecycle.ViewModelProvider; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarService; +import org.briarproject.briar.android.account.SetupActivity; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.activity.BaseActivity; +import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; +import org.briarproject.briar.android.login.StartupViewModel.State; + +import javax.inject.Inject; + +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 org.briarproject.briar.android.login.StartupViewModel.State.SIGNED_IN; +import static org.briarproject.briar.android.login.StartupViewModel.State.SIGNED_OUT; +import static org.briarproject.briar.android.login.StartupViewModel.State.STARTED; +import static org.briarproject.briar.android.login.StartupViewModel.State.STARTING; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class StartupActivity extends BaseActivity implements + BaseFragmentListener { + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private StartupViewModel viewModel; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + // fade-in after splash screen instead of default animation + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + + setContentView(R.layout.activity_fragment_container); + + viewModel = ViewModelProviders.of(this, viewModelFactory) + .get(StartupViewModel.class); + if (!viewModel.accountExists()) { + // TODO ideally we would not have to delete the account again + // The account needs to deleted again to remove the database folder, + // because if it exists, we assume the database also exists + // and when clearing app data, the folder does not get deleted. + viewModel.deleteAccount(); + onAccountDeleted(true); + return; + } + viewModel.getAccountDeleted().observe(this, this::onAccountDeleted); + viewModel.getState().observe(this, this::onStateChanged); + } + + @Override + public void onStart() { + super.onStart(); + viewModel.clearSignInNotification(); + } + + @Override + public void onBackPressed() { + // Move task and activity to the background instead of showing another + // password prompt. + // onActivityResult() won't be called in BriarActivity + moveTaskToBack(true); + } + + private void onStateChanged(State state) { + if (state == SIGNED_OUT) { + showInitialFragment(new PasswordFragment()); + } else if (state == SIGNED_IN) { + startService(new Intent(this, BriarService.class)); + } else if (state == STARTING) { + // only show OpenDatabaseFragment if not already visible + FragmentManager fm = getSupportFragmentManager(); + Fragment f = fm.findFragmentByTag(OpenDatabaseFragment.TAG); + if (f == null || !f.isVisible()) { + showNextFragment(new OpenDatabaseFragment()); + } + } else if (state == STARTED) { + setResult(RESULT_OK); + supportFinishAfterTransition(); + overridePendingTransition(R.anim.screen_new_in, + R.anim.screen_old_out); + } + } + + private void onAccountDeleted(boolean accountDeleted) { + if (accountDeleted) { + setResult(RESULT_CANCELED); + finish(); + Intent i = new Intent(this, SetupActivity.class); + i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP | + FLAG_ACTIVITY_CLEAR_TASK); + startActivity(i); + } + } + + @Override + public void runOnDbThread(Runnable runnable) { + // we don't need this and shouldn't be forced to implement it + throw new AssertionError(); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/StartupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/login/StartupViewModel.java new file mode 100644 index 000000000..a152c863b --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/StartupViewModel.java @@ -0,0 +1,129 @@ +package org.briarproject.briar.android.login; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.account.AccountManager; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState; +import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.api.android.AndroidNotificationManager; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; +import static org.briarproject.briar.android.login.StartupViewModel.State.COMPACTING; +import static org.briarproject.briar.android.login.StartupViewModel.State.MIGRATING; +import static org.briarproject.briar.android.login.StartupViewModel.State.SIGNED_IN; +import static org.briarproject.briar.android.login.StartupViewModel.State.SIGNED_OUT; +import static org.briarproject.briar.android.login.StartupViewModel.State.STARTED; +import static org.briarproject.briar.android.login.StartupViewModel.State.STARTING; + +@NotNullByDefault +public class StartupViewModel extends AndroidViewModel + implements EventListener { + + enum State {SIGNED_OUT, SIGNED_IN, STARTING, MIGRATING, COMPACTING, STARTED} + + private final AccountManager accountManager; + private final AndroidNotificationManager notificationManager; + private final EventBus eventBus; + @IoExecutor + private final Executor ioExecutor; + + private final MutableLiveData passwordValidated = + new MutableLiveData<>(); + private final MutableLiveData accountDeleted = + new MutableLiveData<>(); + private final MutableLiveData state = new MutableLiveData<>(); + + @Inject + StartupViewModel(Application app, + AccountManager accountManager, + LifecycleManager lifecycleManager, + AndroidNotificationManager notificationManager, + EventBus eventBus, + @IoExecutor Executor ioExecutor) { + super(app); + this.accountManager = accountManager; + this.notificationManager = notificationManager; + this.eventBus = eventBus; + this.ioExecutor = ioExecutor; + + updateState(lifecycleManager.getLifecycleState()); + eventBus.addListener(this); + } + + @Override + protected void onCleared() { + eventBus.removeListener(this); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof LifecycleEvent) { + LifecycleState s = ((LifecycleEvent) e).getLifecycleState(); + updateState(s); + } + } + + @UiThread + private void updateState(LifecycleState s) { + if (accountManager.hasDatabaseKey()) { + if (s.isAfter(STARTING_SERVICES)) state.setValue(STARTED); + else if (s == MIGRATING_DATABASE) state.setValue(MIGRATING); + else if (s == COMPACTING_DATABASE) state.setValue(COMPACTING); + else state.setValue(STARTING); + } else { + state.setValue(SIGNED_OUT); + } + } + + boolean accountExists() { + return accountManager.accountExists(); + } + + void clearSignInNotification() { + notificationManager.blockSignInNotification(); + notificationManager.clearSignInNotification(); + } + + void validatePassword(String password) { + ioExecutor.execute(() -> { + boolean signedIn = accountManager.signIn(password); + passwordValidated.postValue(signedIn); + if (signedIn) state.postValue(SIGNED_IN); + }); + } + + MutableLiveData getPasswordValidated() { + return passwordValidated; + } + + LiveData getAccountDeleted() { + return accountDeleted; + } + + LiveData getState() { + return state; + } + + @UiThread + void deleteAccount() { + accountManager.deleteAccount(); + accountDeleted.setValue(true); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index b8e46d569..85bb7b2b1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -40,7 +40,6 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.util.StringUtils; import org.briarproject.briar.R; import org.briarproject.briar.android.Localizer; -import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.util.UiUtils; import java.util.ArrayList; @@ -80,6 +79,7 @@ import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; +import static org.briarproject.briar.android.activity.ActivityComponent.ENTRY_ACTIVITY; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_SIGN_OUT; import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; @@ -192,7 +192,7 @@ public class SettingsFragment extends PreferenceFragmentCompat // bring up parent activity, so it can change its theme as well // upstream bug: https://issuetracker.google.com/issues/38352704 Intent intent = - new Intent(getActivity(), NavDrawerActivity.class); + new Intent(getActivity(), ENTRY_ACTIVITY); intent.setFlags( FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); startActivity(intent); @@ -575,8 +575,7 @@ public class SettingsFragment extends PreferenceFragmentCompat builder.setPositiveButton(R.string.sign_out_button, (dialogInterface, i) -> { language.setValue(newValue); - Intent intent = new Intent(getContext(), - NavDrawerActivity.class); + Intent intent = new Intent(getContext(), ENTRY_ACTIVITY); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.putExtra(INTENT_SIGN_OUT, true); requireActivity().startActivity(intent); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java index 5b80d3cc3..b3030e52e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java @@ -2,10 +2,8 @@ package org.briarproject.briar.android.splash; import android.app.Activity; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.support.v7.preference.PreferenceManager; import android.transition.Fade; import org.briarproject.bramble.api.account.AccountManager; @@ -15,10 +13,6 @@ import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BaseActivity; -import org.briarproject.briar.android.login.OpenDatabaseActivity; -import org.briarproject.briar.android.login.SetupActivity; -import org.briarproject.briar.android.navdrawer.NavDrawerActivity; -import org.briarproject.briar.api.android.LockManager; import java.util.logging.Logger; @@ -27,27 +21,35 @@ import javax.inject.Inject; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.os.Build.VERSION.SDK_INT; +import static android.support.v7.preference.PreferenceManager.setDefaultValues; +import static java.lang.System.currentTimeMillis; +import static java.util.logging.Logger.getLogger; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; +import static org.briarproject.briar.android.activity.ActivityComponent.ENTRY_ACTIVITY; @MethodsNotNullByDefault @ParametersNotNullByDefault public class SplashScreenActivity extends BaseActivity { private static final Logger LOG = - Logger.getLogger(SplashScreenActivity.class.getName()); + getLogger(SplashScreenActivity.class.getName()); @Inject protected AccountManager accountManager; @Inject - protected LockManager lockManager; - @Inject protected AndroidExecutor androidExecutor; + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); - if (Build.VERSION.SDK_INT >= 21) { + if (SDK_INT >= 21) { getWindow().setExitTransition(new Fade()); } @@ -56,44 +58,21 @@ public class SplashScreenActivity extends BaseActivity { setContentView(R.layout.splash); if (accountManager.hasDatabaseKey()) { - if (lockManager.isLocked()) { - // The database needs to be opened for the app to be locked. - // Start main activity right away. It will open UnlockActivity. - // Otherwise, we would end up with two screen unlock inputs. - startNextActivity(NavDrawerActivity.class); - } else { - startNextActivity(OpenDatabaseActivity.class); - } + startNextActivity(ENTRY_ACTIVITY); finish(); } else { new Handler().postDelayed(() -> { - startNextActivity(); + if (currentTimeMillis() >= EXPIRY_DATE) { + LOG.info("Expired"); + startNextActivity(ExpiredActivity.class); + } else { + startNextActivity(ENTRY_ACTIVITY); + } supportFinishAfterTransition(); }, 500); } } - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - - private void startNextActivity() { - if (System.currentTimeMillis() >= EXPIRY_DATE) { - LOG.info("Expired"); - startNextActivity(ExpiredActivity.class); - } else { - if (accountManager.accountExists()) { - LOG.info("Account exists"); - startNextActivity(OpenDatabaseActivity.class); - } else { - LOG.info("Account does not exist"); - accountManager.deleteAccount(); - startNextActivity(SetupActivity.class); - } - } - } - private void startNextActivity(Class activityClass) { Intent i = new Intent(this, activityClass); i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); @@ -101,8 +80,8 @@ public class SplashScreenActivity extends BaseActivity { } private void setPreferencesDefaults() { - androidExecutor.runOnBackgroundThread(() -> - PreferenceManager.setDefaultValues(SplashScreenActivity.this, + androidExecutor.runOnBackgroundThread( + () -> setDefaultValues(SplashScreenActivity.this, R.xml.panic_preferences, false)); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/test/TestDataActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/test/TestDataActivity.java index fad48f249..7e7eb8024 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/test/TestDataActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/test/TestDataActivity.java @@ -11,12 +11,12 @@ import android.widget.TextView; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; -import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.api.test.TestDataCreator; import javax.inject.Inject; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static org.briarproject.briar.android.activity.ActivityComponent.ENTRY_ACTIVITY; public class TestDataActivity extends BriarActivity { @@ -154,7 +154,7 @@ public class TestDataActivity extends BriarActivity { testDataCreator.createTestData(contactsSeekBar.getProgress() + 1, messagesSeekBar.getProgress(), blogPostsSeekBar.getProgress(), forumsSeekBar.getProgress(), forumPostsSeekBar.getProgress()); - Intent intent = new Intent(this, NavDrawerActivity.class); + Intent intent = new Intent(this, ENTRY_ACTIVITY); intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); finish(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index eb7ecd9af..fd42e26bd 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -36,6 +36,7 @@ import android.transition.Transition; import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; +import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import org.acra.ACRA; @@ -56,6 +57,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.Build.MANUFACTURER; import static android.os.Build.VERSION.SDK_INT; import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS; +import static android.support.v4.content.ContextCompat.getSystemService; import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL; import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_AUTO; import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; @@ -76,6 +78,8 @@ import static android.text.format.DateUtils.YEAR_IN_MILLIS; import static android.view.KeyEvent.ACTION_DOWN; import static android.view.KeyEvent.KEYCODE_ENTER; import static android.view.inputmethod.EditorInfo.IME_NULL; +import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT; +import static java.util.Objects.requireNonNull; import static org.briarproject.briar.BuildConfig.APPLICATION_ID; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; @@ -87,6 +91,18 @@ public class UiUtils { public static final int TEASER_LENGTH = 320; public static final float GREY_OUT = 0.5f; + public static void showSoftKeyboard(View view) { + InputMethodManager imm = requireNonNull( + getSystemService(view.getContext(), InputMethodManager.class)); + imm.showSoftInput(view, SHOW_IMPLICIT); + } + + public static void hideSoftKeyboard(View view) { + InputMethodManager imm = requireNonNull( + getSystemService(view.getContext(), InputMethodManager.class)); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + public static String getContactDisplayName(Author author, @Nullable String alias) { String name = author.getName(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java index d35f2bb5b..86864b692 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java @@ -7,6 +7,7 @@ import org.briarproject.briar.android.contact.add.remote.AddContactViewModel; import org.briarproject.briar.android.contact.add.remote.PendingContactListViewModel; import org.briarproject.briar.android.conversation.ConversationViewModel; import org.briarproject.briar.android.conversation.ImageViewModel; +import org.briarproject.briar.android.login.StartupViewModel; import javax.inject.Singleton; @@ -17,6 +18,11 @@ import dagger.multibindings.IntoMap; @Module public abstract class ViewModelModule { + @Binds + @IntoMap + @ViewModelKey(StartupViewModel.class) + abstract ViewModel bindStartupViewModel(StartupViewModel startupViewModel); + @Binds @IntoMap @ViewModelKey(ConversationViewModel.class) diff --git a/briar-android/src/main/res/layout/activity_unlock.xml b/briar-android/src/main/res/layout/activity_unlock.xml index 83c8d594b..4a157595a 100644 --- a/briar-android/src/main/res/layout/activity_unlock.xml +++ b/briar-android/src/main/res/layout/activity_unlock.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".android.login.UnlockActivity"> + tools:context=".android.account.UnlockActivity"> + android:layout_height="match_parent" + tools:context=".android.login.PasswordFragment"> - - - > resultCaptor; diff --git a/briar-android/src/test/java/org/briarproject/briar/android/login/PasswordControllerImplTest.java b/briar-android/src/test/java/org/briarproject/briar/android/login/ChangePasswordControllerImplTest.java similarity index 87% rename from briar-android/src/test/java/org/briarproject/briar/android/login/PasswordControllerImplTest.java rename to briar-android/src/test/java/org/briarproject/briar/android/login/ChangePasswordControllerImplTest.java index 987155ffc..a588cb71b 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/login/PasswordControllerImplTest.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/login/ChangePasswordControllerImplTest.java @@ -14,7 +14,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.briarproject.bramble.util.StringUtils.getRandomString; -public class PasswordControllerImplTest extends BrambleMockTestCase { +public class ChangePasswordControllerImplTest extends BrambleMockTestCase { private final AccountManager accountManager = context.mock(AccountManager.class); @@ -33,7 +33,7 @@ public class PasswordControllerImplTest extends BrambleMockTestCase { will(returnValue(true)); }}); - PasswordControllerImpl p = new PasswordControllerImpl(accountManager, + ChangePasswordControllerImpl p = new ChangePasswordControllerImpl(accountManager, ioExecutor, estimator); AtomicBoolean capturedResult = new AtomicBoolean(false); @@ -48,7 +48,7 @@ public class PasswordControllerImplTest extends BrambleMockTestCase { will(returnValue(false)); }}); - PasswordControllerImpl p = new PasswordControllerImpl(accountManager, + ChangePasswordControllerImpl p = new ChangePasswordControllerImpl(accountManager, ioExecutor, estimator); AtomicBoolean capturedResult = new AtomicBoolean(true); diff --git a/briar-android/src/test/java/org/briarproject/briar/android/login/TestChangePasswordActivity.java b/briar-android/src/test/java/org/briarproject/briar/android/login/TestChangePasswordActivity.java index c27ad7e11..594866115 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/login/TestChangePasswordActivity.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/login/TestChangePasswordActivity.java @@ -6,7 +6,8 @@ package org.briarproject.briar.android.login; */ public class TestChangePasswordActivity extends ChangePasswordActivity { - public void setPasswordController(PasswordController passwordController) { + public void setPasswordController( + ChangePasswordController passwordController) { this.passwordController = passwordController; }